diff --git a/packages/builder/.chglog/CHANGELOG.tpl.md b/packages/builder/.chglog/CHANGELOG.tpl.md new file mode 100755 index 00000000000..96e4d9d73c4 --- /dev/null +++ b/packages/builder/.chglog/CHANGELOG.tpl.md @@ -0,0 +1,1033 @@ +# Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +{{ if .Versions -}} +A list of unreleased changes can be found [here]({{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD). +{{ end -}} + +{{ range .Versions }} + +## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} +{{ range .CommitGroups -}} +### {{ .Title }} +{{ range .Commits -}} +- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} [`{{ .Hash.Short }}`]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Long }}) +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +### Reverts +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} +{{ end -}} + + +## [v3.5.1] - 2024-07-22 +### Bug Fixes +- **generateThemeDesignerResources:** Allow core .theming in sources ([#1062](https://github.com/SAP/ui5-builder/issues/1062)) [`dda3011`](https://github.com/SAP/ui5-builder/commit/dda30114461314e81da43bd739e709186744f592) + + +## [v3.5.0] - 2024-06-21 +### Features +- ES2023 Support [`097049d`](https://github.com/SAP/ui5-builder/commit/097049daeec5c54c2d5e858b12e0c54c826ff663) + + + +## [v3.4.1] - 2024-05-13 +### Bug Fixes +- **bundle/Builder:** Correct bundling of resources with empty source map [`1228db7`](https://github.com/SAP/ui5-builder/commit/1228db78e7e655cea58c20517662b08dd09db87b) + + + +## [v3.4.0] - 2024-04-24 +### Features +- **generateFlexChangesBundle:** Handle minUI5Version as array ([#1009](https://github.com/SAP/ui5-builder/issues/1009)) [`45c9b7e`](https://github.com/SAP/ui5-builder/commit/45c9b7efc255e1d62dfed82ccf67b61d54dd8627) + + + +## [v3.3.1] - 2024-03-27 + + +## [v3.3.0] - 2024-01-18 +### Features +- **flexChangesBundler:** Merge existing with new flexibility-bundle.json [`fd070ab`](https://github.com/SAP/ui5-builder/commit/fd070abaa22680ee70b448fa0ac406a8f2b57cc5) + + + +## [v3.2.0] - 2023-12-12 +### Bug Fixes +- Incomplete multi-character sanitization ([#959](https://github.com/SAP/ui5-builder/issues/959)) [`d61f1b7`](https://github.com/SAP/ui5-builder/commit/d61f1b744495f5428db33467218077e7996f1575) +- Add guard against prototype pollution ([#960](https://github.com/SAP/ui5-builder/issues/960)) [`ba230d9`](https://github.com/SAP/ui5-builder/commit/ba230d922cac0acd291dfe18b0ae7a95eae8b190) +- Incomplete string escaping or encoding ([#958](https://github.com/SAP/ui5-builder/issues/958)) [`50bb0d9`](https://github.com/SAP/ui5-builder/commit/50bb0d97e76fb312412cf29fae18b76cc88df6f4) +- **manifestCreator:** set fallbackLocale to empty string if no locale is present ([#962](https://github.com/SAP/ui5-builder/issues/962)) [`26526a0`](https://github.com/SAP/ui5-builder/commit/26526a08ff38ee11ed3bd506f7ef0610f1d1ccb0) + +### Features +- depCache bundling mode ([#951](https://github.com/SAP/ui5-builder/issues/951)) [`f2cf564`](https://github.com/SAP/ui5-builder/commit/f2cf564f0f71d635e58a743c7bdef1f427e341b2) + + + +## [v3.1.1] - 2023-11-20 +### Bug Fixes +- Handle graceful termination of workerpool for parallel builds ([#953](https://github.com/SAP/ui5-builder/issues/953)) [`f7b9f27`](https://github.com/SAP/ui5-builder/commit/f7b9f27ac5966bd89e52e4c2d5b03482a0f3dbb7) +- **Bundling:** Detect manifest.json dependency of libraries [`6f9995f`](https://github.com/SAP/ui5-builder/commit/6f9995f5b47a6094fa93b5d433be849b1d3cdc7e) + + + +## [v3.1.0] - 2023-10-11 +### Bug Fixes +- **bundle/Builder:** Add missing 'names' attribute to generated source maps [`57e0e50`](https://github.com/SAP/ui5-builder/commit/57e0e5047638a9a704a696b8af7780fbbceefbc4) + +### Features +- Validate apiVersion property in Library.init ([#943](https://github.com/SAP/ui5-builder/issues/943)) [`52bf258`](https://github.com/SAP/ui5-builder/commit/52bf25842a59c3fa1ddbe71b482b232e18b55288) +- **Minifier:** Support input source maps ([#780](https://github.com/SAP/ui5-builder/issues/780)) [`67ecb27`](https://github.com/SAP/ui5-builder/commit/67ecb27e44a2d84e6b2203f31049220dcbcd41f0) + +### Reverts +- [INTERNAL] Azure Pipelines: Switch back to Node 20.5.x + + + +## [v3.0.9] - 2023-07-26 + + +## [v3.0.8] - 2023-07-19 +### Bug Fixes +- Revert "[INTERNAL] Remove implicit dependencies concept ([#913](https://github.com/SAP/ui5-builder/issues/913))" [`1043714`](https://github.com/SAP/ui5-builder/commit/1043714e3b952a8280f1ff7909f79db3b750eb0c) +- **generateJsdoc:** Also process resources created by preceeding tasks [`04447bd`](https://github.com/SAP/ui5-builder/commit/04447bdec28b8bf18c24bd53e3fe8be9bdeed6c2) + + + +## [v3.0.7] - 2023-07-13 +### Bug Fixes +- Don't report a missing module that's contained in another bundle ([#915](https://github.com/SAP/ui5-builder/issues/915)) [`8f23f38`](https://github.com/SAP/ui5-builder/commit/8f23f388f6d64f313ae8a89d0fcaf39ba905a70b) + + + +## [v3.0.6] - 2023-06-21 + + +## [v3.0.5] - 2023-06-05 +### Bug Fixes +- **bundle/Builder:** Remove sourceMappingURL from modules embedded as string [`a2f410c`](https://github.com/SAP/ui5-builder/commit/a2f410c32945a6a25fdf47b7b06ccb7f21ef5716) + +### Dependency Updates +- Bump xml2js from 0.5.0 to 0.6.0 [`9679830`](https://github.com/SAP/ui5-builder/commit/967983000d64324089740c5ffc0460dcca23866f) + + + +## [v3.0.4] - 2023-05-23 +### Bug Fixes +- **lbt/Resolver:** ensure stable module order in generated bundleInfo [`482b8a0`](https://github.com/SAP/ui5-builder/commit/482b8a011d9f0e3fc24d0485be7caf1509195c82) + + + +## [v3.0.3] - 2023-04-21 +### Dependency Updates +- Bump rimraf from 4.4.1 to 5.0.0 ([#897](https://github.com/SAP/ui5-builder/issues/897)) [`7b5e442`](https://github.com/SAP/ui5-builder/commit/7b5e442be80ff2e070c249a2c3c3cbe11a34b956) + + + +## [v3.0.2] - 2023-04-12 +### Dependency Updates +- Bump xml2js from 0.4.23 to 0.5.0 [`5269fc2`](https://github.com/SAP/ui5-builder/commit/5269fc206b30a13d28493892cc658f1614e7cc74) + + + +## [v3.0.1] - 2023-02-16 + + +## [v3.0.0] - 2023-02-09 +### Breaking Changes +- Transform to ES Modules ([#790](https://github.com/SAP/ui5-builder/issues/790)) [`a439aa9`](https://github.com/SAP/ui5-builder/commit/a439aa9f679dfe2e18b8c540f54876f94c0de119) +- **generateJsDoc:** Remove internal 'buildContext' parameter [`4ec80c8`](https://github.com/SAP/ui5-builder/commit/4ec80c874e177b658b1cd819431b6cb0660ded87) +- Removal of manifestBundler and generateManifestBundle ([#838](https://github.com/SAP/ui5-builder/issues/838)) [`07a5be2`](https://github.com/SAP/ui5-builder/commit/07a5be2b6d9aa23cf78ddd17951c832d6dec7bef) +- **JSDoc:** Fail build when jsdoc command failed ([#845](https://github.com/SAP/ui5-builder/issues/845)) [`c2916b4`](https://github.com/SAP/ui5-builder/commit/c2916b4f1d49b5500e4b51143d4e6065ac200eef) +- Remove build execution ([#740](https://github.com/SAP/ui5-builder/issues/740)) [`af2e956`](https://github.com/SAP/ui5-builder/commit/af2e956f6cba1275669160328e32df3fffa782d0) +- Enable modern preload bundles without dependencies ([#739](https://github.com/SAP/ui5-builder/issues/739)) [`97cfa6c`](https://github.com/SAP/ui5-builder/commit/97cfa6cd3eadff57bcc18816534c7751551ebdb8) +- Require Node.js >= 16.18.0 / npm >= 8 [`dbf6c06`](https://github.com/SAP/ui5-builder/commit/dbf6c0694693070f73e9f96edef90f094d5bf721) +- **generateResourcesJson:** Make 'dependencies' parameter mandatory [`268dd16`](https://github.com/SAP/ui5-builder/commit/268dd16091c515ec0c922ea23af437d9aa8bf3ea) +- **moduleBundler:** Always default to `optimize: true` ([#685](https://github.com/SAP/ui5-builder/issues/685)) [`de5837c`](https://github.com/SAP/ui5-builder/commit/de5837c43449ea61deae3b2f02f9973f3fa37992) +- **libraryLessGenerator:** Throw error when import can't be inlined [`d2be9bb`](https://github.com/SAP/ui5-builder/commit/d2be9bb68600ec97b9bc007f348f87ad456fb5a3) + +### BREAKING CHANGE +This package has been transformed to ES Modules. Therefore it no longer provides a CommonJS export. +If your project uses CommonJS, it needs to be converted to ESM or use a dynamic import. + +For more information see also: + +- https://sap.github.io/ui5-tooling/updates/migrate-v3/ +- https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +The `jsdocGenerator` processor and the corresponding `generateJsdoc` task will now throw an error when JSDoc reports an error (exit code != 0). This will also fail the build when running `ui5 build jsdoc`. + +The manifestBundler processor and generateManifestBundle task has been removed because it is no longer required for the HTML5 repository in Cloud Foundry. + +- builder.js has been removed. Use ui5-project builder instead +- Tasks now rely on Project instances being available on Resources (see https://github.com/SAP/ui5-fs/pull/381) +- TaskRepository#addTask has been removed. Custom tasks need to be added to the project graph instead +- TaskUtil#get/set/clearTag now requires a Project instance to be provided. Path strings are no longer sufficient + and will cause an exception to be thrown + +For library projects, the task "generateComponentPreload" is now +executed after tasks "generateLibraryManifest" and +"generateManifestBundle" instead of before them. + +Support for older Node.js and npm releases has been dropped. +Only Node.js v16.18.0 and npm v8 or higher are supported. + +The following tasks have been removed: + +- createDebugFiles +- uglify + +The following processors have been removed: + +- debugFileCreator +- resourceCopier +- uglifier + +As a replacement, the new 'minify' task and 'minifier' processor can be +used. + +Note: The minify task is executed earlier, before the bundling +process takes place. Existing 'beforeTask' and 'afterTask' configuration of +custom tasks might need to be adapted to cater for this change. + +### Features +- Support ES2022 language features ([#848](https://github.com/SAP/ui5-builder/issues/848)) [`f9b8457`](https://github.com/SAP/ui5-builder/commit/f9b845726731a0e02ec4a499e2a1a82a639174a8) +- **jsdoc:** Support destructuring of enums for defaultValue ([#775](https://github.com/SAP/ui5-builder/issues/775)) [`523f365`](https://github.com/SAP/ui5-builder/commit/523f365cb917997c5031d245309c21e9e4b3e311) +- **builder:** Improve support for ES6+ syntax ([#774](https://github.com/SAP/ui5-builder/issues/774)) [`90385fe`](https://github.com/SAP/ui5-builder/commit/90385feb7cea1e5b864cff035b01263a492972a9) +- **jsdoc:** Improve support for ES6+ syntax ([#785](https://github.com/SAP/ui5-builder/issues/785)) [`187a6a3`](https://github.com/SAP/ui5-builder/commit/187a6a35000145d70bf41e0d8b724e5ea8d8dc78) +- **generateThemeDesignerResources task:** Create css_variables.less ([#730](https://github.com/SAP/ui5-builder/issues/730)) [`34e69be`](https://github.com/SAP/ui5-builder/commit/34e69be95fc8ec1961b24b7e2580c2c993d814d3) +- **builder:** Add cssVariables option ([#728](https://github.com/SAP/ui5-builder/issues/728)) [`30d58e1`](https://github.com/SAP/ui5-builder/commit/30d58e1081c1bdc665f13952ecbe5c400b5f4ed7) +- Generate source maps for bundles ([#695](https://github.com/SAP/ui5-builder/issues/695)) [`8a20c42`](https://github.com/SAP/ui5-builder/commit/8a20c4257a1ebe4d613b0595be93cd45d900f580) +- **replaceVersion/Copyright:** Also process test-resources ([#693](https://github.com/SAP/ui5-builder/issues/693)) [`a0d4bc2`](https://github.com/SAP/ui5-builder/commit/a0d4bc2a80d61e042786207af4bdbda6c3ddfe31) +- Support ES2022 language features [`e749b6a`](https://github.com/SAP/ui5-builder/commit/e749b6ae0838f923b27dd0d7d34da2174c433f5f) +- Add minify task and processor ([#666](https://github.com/SAP/ui5-builder/issues/666)) [`a3af604`](https://github.com/SAP/ui5-builder/commit/a3af604ff0a12fb9241dcd105cf0aec8d718a299) + +### Bug Fixes +- **package.json:** Downgrade es5-ext dependency [`2b71707`](https://github.com/SAP/ui5-builder/commit/2b71707683480b26aab9957c09e5d22bce8a6450) +- **minify:** Change default to not omit source map resources [`6d75a49`](https://github.com/SAP/ui5-builder/commit/6d75a495224590097332fc061c66cfe0e8f4c151) +- Avoid redundant bundle creation ([#741](https://github.com/SAP/ui5-builder/issues/741)) [`13c8405`](https://github.com/SAP/ui5-builder/commit/13c840585946401d238936dfa38a6f70a73e9ed0) +- **JSModuleAnalyzer:** Fix detection of bundle name ([#705](https://github.com/SAP/ui5-builder/issues/705)) [`aaeafd4`](https://github.com/SAP/ui5-builder/commit/aaeafd4a1fd194dd08e5ae47c29d90f0b4c7d197) +- **generateResourcesJson:** Add raw-module info for debug bundles ([#736](https://github.com/SAP/ui5-builder/issues/736)) [`3b918e8`](https://github.com/SAP/ui5-builder/commit/3b918e83bfd38342778ecd4c58e648e99ad7cffc) +- **LibraryBuilder:** Align task order of "generateComponentPreload" [`aea061d`](https://github.com/SAP/ui5-builder/commit/aea061d9d6c2ac0c11484dcc08bdcda23ab62986) +- **LocatorResourcePool:** Wait for resources in prepare step ([#719](https://github.com/SAP/ui5-builder/issues/719)) [`1b7f93f`](https://github.com/SAP/ui5-builder/commit/1b7f93f4988340d7a6575be3191a02e6c295ebd0) +- **XMLTemplateAnalyzer:** Analyze core:require of FragmentDefinition [`af075ed`](https://github.com/SAP/ui5-builder/commit/af075edf784d9f1ba162a34f0bf150dbcbc0f479) +- **Bundler:** Ensure reproducibility for bundles with multiple parts ([#689](https://github.com/SAP/ui5-builder/issues/689)) [`6f4588b`](https://github.com/SAP/ui5-builder/commit/6f4588b3eb687178e557ac4fb36481104f9681a0) +- **generateResourcesJson:** Don't list resources omitted from build result [`9608c51`](https://github.com/SAP/ui5-builder/commit/9608c5177f86494ba689d2e799b4378fec9ed6fa) +- **generateResourcesJson:** Analyze debug bundles ([#669](https://github.com/SAP/ui5-builder/issues/669)) [`f27513a`](https://github.com/SAP/ui5-builder/commit/f27513a259b30d39e260790781b6d435b4ab088b) +- **Bundler:** Sort raw modules by default [`0e11b69`](https://github.com/SAP/ui5-builder/commit/0e11b6965a1d2e63a96e9b738e38975b5ac755bc) + +### Dependency Updates +- Bump less-openui5 from 0.11.4 to 0.11.5 [`bf46354`](https://github.com/SAP/ui5-builder/commit/bf463541b7651db1dee167057eddc94181f5c1da) +- Bump less-openui5 from 0.11.3 to 0.11.4 [`b7a507f`](https://github.com/SAP/ui5-builder/commit/b7a507fbc34076f5bf95f92f058fd2c58d79a455) +- Bump less-openui5 from 0.11.2 to 0.11.3 [`28e684b`](https://github.com/SAP/ui5-builder/commit/28e684b85e7eb621f210041702e2a316d1d0eb62) +- Bump escope from 3.6.0 to 4.0.0 [`e25af0d`](https://github.com/SAP/ui5-builder/commit/e25af0d9c1184916aa037b4b4ca23d15c5f2a408) +- Bump rimraf from 3.0.2 to 4.1.1 ([#873](https://github.com/SAP/ui5-builder/issues/873)) [`ddb9660`](https://github.com/SAP/ui5-builder/commit/ddb96602fea5b96ba37afdebda0993d847462f52) +- Bump jsdoc from 3.6.11 to 4.0.0 ([#821](https://github.com/SAP/ui5-builder/issues/821)) [`6d05117`](https://github.com/SAP/ui5-builder/commit/6d0511785069d12a588b2ad9fa31de828700fef0) + + +## [v2.11.9] - 2022-11-30 +### Bug Fixes +- **transformApiJson:** Fix regression in UI5 Tooling scenario [`819c808`](https://github.com/SAP/ui5-builder/commit/819c808010963b84bfe33e109a7b2a5412096f78) + +### Dependency Updates +- Bump less-openui5 from 0.11.2 to 0.11.3 [`1bbe5f5`](https://github.com/SAP/ui5-builder/commit/1bbe5f561fb35c646a27cbe4d03050101ff68d7c) + + + +## [v2.11.8] - 2022-10-28 +### Bug Fixes +- **ComponentAnalyzer:** Fix detection of rootView [`acb3d9f`](https://github.com/SAP/ui5-builder/commit/acb3d9fd7fc60ad61ac88d68cb0fd5296b795e9f) +- **XMLTemplateAnalyzer:** Fix detection of nested views [`1cbfd82`](https://github.com/SAP/ui5-builder/commit/1cbfd82396e5fa78455e0a696e9229a2e8a337fc) + + + +## [v2.11.7] - 2022-10-20 +### Dependency Updates +- Bump escope from 3.6.0 to 4.0.0 [`852b37f`](https://github.com/SAP/ui5-builder/commit/852b37f549f3927decbc1b40cc6055325472d625) + +### Reverts +- [FIX] package.json: Downgrade es5-ext dependency ([#798](https://github.com/SAP/ui5-builder/issues/798)) + + + +## [v2.11.6] - 2022-10-12 +### Bug Fixes +- **package.json:** Downgrade es5-ext dependency ([#798](https://github.com/SAP/ui5-builder/issues/798)) [`0e18008`](https://github.com/SAP/ui5-builder/commit/0e18008d86da93c5c8743b17ac0091afc3064d78) + + + +## [v2.11.5] - 2022-05-04 + + + +## [v2.11.4] - 2022-02-21 +### Bug Fixes +- **generateFlexChangesBundle:** Fix minUI5Version check for UI5 v1.100.0+ ([#706](https://github.com/SAP/ui5-builder/issues/706)) [`fb1217a`](https://github.com/SAP/ui5-builder/commit/fb1217ac536c20da81526f82f2ecb03686815942) + + + +## [v2.11.3] - 2021-12-14 +### Bug Fixes +- Enable buildThemes for libraries without .library [`7b941a7`](https://github.com/SAP/ui5-builder/commit/7b941a797210463a9fa8ca50753662c5db373aa6) + + + +## [v2.11.2] - 2021-11-17 +### Bug Fixes +- **Builder:** Emit warning on bundleInfo name without extension [`ed0da12`](https://github.com/SAP/ui5-builder/commit/ed0da123ac084d126a28f0f34d9740c310d91902) +- **manifestCreator:** Trim whitespace/new-lines for sap.app/title [`019cfd7`](https://github.com/SAP/ui5-builder/commit/019cfd7031a2bd63ce93801ad027413151a3b060) + + + +## [v2.11.1] - 2021-10-19 +### Bug Fixes +- Minification excludes for application projects [`7f6fd68`](https://github.com/SAP/ui5-builder/commit/7f6fd68e1aed9131896723f1231816287eaf2fce) + + + +## [v2.11.0] - 2021-10-19 +### Bug Fixes +- **manifestCreator:** supportedThemes should only list relevant themes [`01f3859`](https://github.com/SAP/ui5-builder/commit/01f3859070b6955b9824b0949e633c6d40244633) + +### Features +- Support build minification excludes ([#653](https://github.com/SAP/ui5-builder/issues/653)) [`0aa2301`](https://github.com/SAP/ui5-builder/commit/0aa2301df4c5d40c531da52e2d6314955b95b396) + + + +## [v2.10.0] - 2021-10-05 +### Features +- Introduce build task replaceBuildtime [`2ad0960`](https://github.com/SAP/ui5-builder/commit/2ad09603deee3bc26eae36aa36a7a4ac10f83cb0) + + + +## [v2.9.5] - 2021-08-25 +### Bug Fixes +- **AbstractBuilder:** Multiple custom task executions did not run ([#636](https://github.com/SAP/ui5-builder/issues/636)) [`c05587a`](https://github.com/SAP/ui5-builder/commit/c05587a3cab93c8852832098318235dbfbcce49f) +- **Builder:** Handle files without extension ([#637](https://github.com/SAP/ui5-builder/issues/637)) [`07aaa24`](https://github.com/SAP/ui5-builder/commit/07aaa247ae8c07f8c7bb13a93439ef2c1270cb6c) + + + +## [v2.9.4] - 2021-07-23 +### Bug Fixes +- **XMLTemplateAnalyzer:** Include document name for XML parsing errors [`3c88ca4`](https://github.com/SAP/ui5-builder/commit/3c88ca47aae85892360e4e4cf4b50860a0f430e3) + + + +## [v2.9.3] - 2021-07-01 +### Bug Fixes +- **LibraryFormatter:** Fix handling of paths containing special characters [`2093562`](https://github.com/SAP/ui5-builder/commit/2093562adec588a9c1f50125e0b739836e6c76c8) + + + +## [v2.9.2] - 2021-06-17 +### Bug Fixes +- Switch from esprima to espree ([#615](https://github.com/SAP/ui5-builder/issues/615)) [`c3d50e3`](https://github.com/SAP/ui5-builder/commit/c3d50e36aacf374f7616278aa590a6423c3294fe) +- **lbt/resources/ResourceCollector:** Ensure proper matching of indicator files ([#614](https://github.com/SAP/ui5-builder/issues/614)) [`2d3a1d8`](https://github.com/SAP/ui5-builder/commit/2d3a1d86f06f0eb223b7ae9843d6479ebb1e14e4) + + + +## [v2.9.1] - 2021-06-08 +### Bug Fixes +- **JSDoc:** Use namespace to derive uilib name for jsdoc generator [`2077130`](https://github.com/SAP/ui5-builder/commit/2077130d52305e4f46c3230c94001812beb2eb36) +- **JSDoc:** add project name to JSDoc configuration and api.json ([#609](https://github.com/SAP/ui5-builder/issues/609)) [`419ce38`](https://github.com/SAP/ui5-builder/commit/419ce3836bce117fb3d5fc05af11caef5c8a432a) +- **lib/processors/debugFileCreator:** Add -dbg suffix only to files ([#611](https://github.com/SAP/ui5-builder/issues/611)) [`9da2f7b`](https://github.com/SAP/ui5-builder/commit/9da2f7b7b53b41adbce772e9823cee24b50a9aa9) + +### Dependency Updates +- Bump cheerio from 0.22.0 to 1.0.0-rc.9 [`8bdb146`](https://github.com/SAP/ui5-builder/commit/8bdb1462e721127eaf860665a1361b6877e873a1) + + + +## [v2.9.0] - 2021-06-01 +### Features +- Support writing 'bundles' config as part of a bundle ([#396](https://github.com/SAP/ui5-builder/issues/396)) [`b5f372a`](https://github.com/SAP/ui5-builder/commit/b5f372a6ab9c31f3acef41a59e6cecd6681be2dd) + + + +## [v2.8.4] - 2021-05-17 +### Bug Fixes +- **XMLTemplateAnalyzer:** Properly detect conditional dependencies [`0f56490`](https://github.com/SAP/ui5-builder/commit/0f56490cedcdebc363cc7f1d99f03cfde907804f) +- **lbt/bundle/Builder:** Preserve comments in bundles [`8dfa919`](https://github.com/SAP/ui5-builder/commit/8dfa9191d36cb60ce44b64536b6f6b2b766b87c2) + + + +## [v2.8.3] - 2021-04-19 +### Bug Fixes +- Emit warning when including/excluding unknown tasks [`854f456`](https://github.com/SAP/ui5-builder/commit/854f456e06e163e8423702e4fe893905e240adfa) + + + +## [v2.8.2] - 2021-03-11 +### Bug Fixes +- **LibraryFormatter:** Do not throw for missing .library in legacy OpenUI5 theme libraries [`f7e22ba`](https://github.com/SAP/ui5-builder/commit/f7e22ba866bf6a61b0d2932ef18aad53127641fd) + +### Dependency Updates +- Bump less-openui5 from 0.10.0 to 0.11.0 ([#594](https://github.com/SAP/ui5-builder/issues/594)) [`f3d174b`](https://github.com/SAP/ui5-builder/commit/f3d174be1fc2ab66a62632439592b2899680c093) + + + +## [v2.8.1] - 2021-03-04 +### Bug Fixes +- **generateResourcesJson:** Include dependencies of XML resources [`0fc364d`](https://github.com/SAP/ui5-builder/commit/0fc364ded64eb5bae4085397dc1831e04b19edf4) +- **manifestCreator:** 'embeds' should list all components ([#575](https://github.com/SAP/ui5-builder/issues/575)) [`11f823a`](https://github.com/SAP/ui5-builder/commit/11f823a77e72cfa4c096e7e8f4277a6a6b9400b8) +- **moduleBundler:** Apply defaultFileTypes ([#580](https://github.com/SAP/ui5-builder/issues/580)) [`42f6474`](https://github.com/SAP/ui5-builder/commit/42f64744a299e8548f6dbdb7bcbb8b3cef72f1f4) +- **resourceListCreator:** Include dependencies of subModules ([#588](https://github.com/SAP/ui5-builder/issues/588)) [`fe61d6e`](https://github.com/SAP/ui5-builder/commit/fe61d6eba6671ca31f7c49a7d1281adb6d5f2114) +- **versionInfoGenerator:** fix hasOwnPreload flag ([#591](https://github.com/SAP/ui5-builder/issues/591)) [`73a0f8b`](https://github.com/SAP/ui5-builder/commit/73a0f8baa0248aef3ac6c2b114082aa120ef6e22) + + + +## [v2.8.0] - 2021-02-09 +### Features +- Add Library/Component preload exclude configuration ([#573](https://github.com/SAP/ui5-builder/issues/573)) [`f1644a4`](https://github.com/SAP/ui5-builder/commit/f1644a4d75c4b3ffb5c092f3a24c74b9123afedc) + + + +## [v2.7.2] - 2021-01-29 +### Dependency Updates +- Bump less-openui5 from 0.9.0 to 0.10.0 [`4674fef`](https://github.com/SAP/ui5-builder/commit/4674fef348cf0f5b1ac76fff931335244d64e66f) + + + +## [v2.7.1] - 2021-01-28 +### Bug Fixes +- **JSDoc:** adapt sdkTransformer to change in transformApiJson.js ([#574](https://github.com/SAP/ui5-builder/issues/574)) [`655f731`](https://github.com/SAP/ui5-builder/commit/655f731191f1210d9f72bee9f60fcebdc863bc36) +- **versionInfoGenerator:** manifest without embeds [#486](https://github.com/SAP/ui5-builder/issues/486) ([#576](https://github.com/SAP/ui5-builder/issues/576)) [`4d86226`](https://github.com/SAP/ui5-builder/commit/4d86226abf6ce549f3cf719068270014ddeefb5a) + + + +## [v2.7.0] - 2021-01-26 +### Features +- Versioninfo enrich with manifest infos ([#554](https://github.com/SAP/ui5-builder/issues/554)) [`7603ece`](https://github.com/SAP/ui5-builder/commit/7603ece36a74592c7756f6147eed91d08a5788a6) +- Align JSDoc template & plugin with OpenUI5 1.87.0 ([#572](https://github.com/SAP/ui5-builder/issues/572)) [`0cb02ac`](https://github.com/SAP/ui5-builder/commit/0cb02acee2b070889146ef9f725cc139691f0ab2) + + + +## [v2.6.1] - 2021-01-21 +### Bug Fixes +- **Theme Build:** Only process themes within library namespace ([#570](https://github.com/SAP/ui5-builder/issues/570)) [`8cecc01`](https://github.com/SAP/ui5-builder/commit/8cecc01ccbd1a84e2ede91e618f31dcf6c00b3fd) +- **processors/libraryLessGenerator:** Don't throw in case of import errors [`0e25b59`](https://github.com/SAP/ui5-builder/commit/0e25b59fe8e7e37f4f6b5c947a76b4da6f79469f) + + + +## [v2.6.0] - 2021-01-14 +### Features +- Add 'generateThemeDesignerResources' task [`03241c0`](https://github.com/SAP/ui5-builder/commit/03241c0e2599cb0928cbbf34ddc678634b2d5a93) +- Add 'libraryLessGenerator' processor ([#560](https://github.com/SAP/ui5-builder/issues/560)) [`a7e1e5c`](https://github.com/SAP/ui5-builder/commit/a7e1e5c0c4b63d9bab3f6645deff8e0f4187d305) +- **manifestBundler:** Add support for sap.app/i18n/enhanceWith ([#564](https://github.com/SAP/ui5-builder/issues/564)) [`1b7a277`](https://github.com/SAP/ui5-builder/commit/1b7a277aeeba9a43b647a46ae4487878ca2d6219) +- **manifestCreator:** enrich manifest with supportedLocales in i18n (for libraries) ([#547](https://github.com/SAP/ui5-builder/issues/547)) [`8102034`](https://github.com/SAP/ui5-builder/commit/810203477647c52948eb357ce9679373d32dd9b1) + + + +## [v2.5.1] - 2020-12-18 +### Bug Fixes +- **Windows:** Correctly handle project paths containing non-ASCII characters [`b229bf3`](https://github.com/SAP/ui5-builder/commit/b229bf315097591d2e870b74fb2b92b26b178877) + + + +## [v2.5.0] - 2020-12-15 +### Bug Fixes +- **manifestCreator:** Add component path to error logs [`049b9ee`](https://github.com/SAP/ui5-builder/commit/049b9ee22f8bf6c1bb41f9ba32be65a8fce38f23) + +### Features +- **ApplicationFormatter:** Implement manifest.appdescr_variant fallback ([#545](https://github.com/SAP/ui5-builder/issues/545)) [`6d44481`](https://github.com/SAP/ui5-builder/commit/6d44481ad3668758d4c008d28b11cb47ca6bbee1) + + + +## [v2.4.5] - 2020-11-30 +### Bug Fixes +- **generateResourcesJson:** Make resources.json generation deterministic [`41d3335`](https://github.com/SAP/ui5-builder/commit/41d3335bbddaba2e65e3293b37f89010ab0cd6fc) +- **manifestCreator:** Only list components with corresponding 'embeddedBy' ([#555](https://github.com/SAP/ui5-builder/issues/555)) [`89872d7`](https://github.com/SAP/ui5-builder/commit/89872d79623accad1ed148034c1f2fe46e44eeee) + + + +## [v2.4.4] - 2020-11-25 +### Bug Fixes +- **JSModuleAnalyzer:** Properly handle jQuery.sap.registerPreloadedModules calls [`9433f6a`](https://github.com/SAP/ui5-builder/commit/9433f6a989d6fea46f637ac8ff58c739977f456c) + + + +## [v2.4.3] - 2020-11-06 + + +## [v2.4.2] - 2020-11-04 +### Reverts +- [FEATURE] Switch XML minifier from pretty-data to minify-xml + + + +## [v2.4.1] - 2020-11-03 +### Dependency Updates +- Bump minify-xml from 2.1.2 to 2.1.3 [`839d12b`](https://github.com/SAP/ui5-builder/commit/839d12b0b4150ef13c86e639576e5a29854dc7d9) + + + +## [v2.4.0] - 2020-11-03 +### Features +- Tag bundles and ignore them in uglify task ([#535](https://github.com/SAP/ui5-builder/issues/535)) [`b487366`](https://github.com/SAP/ui5-builder/commit/b4873663ea67fa16f8fd9c2672c647026894ba32) +- Switch XML minifier from pretty-data to minify-xml [`be29520`](https://github.com/SAP/ui5-builder/commit/be295203cf71740f0585ee59f44c55ee59e41b26) + + + +## [v2.3.0] - 2020-10-22 +### Features +- Create designtime and support bundles for libraries ([#529](https://github.com/SAP/ui5-builder/issues/529)) [`2a51943`](https://github.com/SAP/ui5-builder/commit/2a5194346279279a6fb28c7332245e1cc5360d63) + +### Performance Improvements +- **BundleWriter:** Improve performance ([#534](https://github.com/SAP/ui5-builder/issues/534)) [`750b43e`](https://github.com/SAP/ui5-builder/commit/750b43eb88aded89eb8cd0b4b9ccb1ca5d5f94d2) + + + +## [v2.2.1] - 2020-10-06 +### Bug Fixes +- **Bundler:** Improve error log messages ([#466](https://github.com/SAP/ui5-builder/issues/466)) [`6bb6235`](https://github.com/SAP/ui5-builder/commit/6bb6235464b54da4e13553ecf9e0fe0ebcb3fe61) +- **tasks/generateResourcesJson:** Handling for sap.ui.integration [`1191b3d`](https://github.com/SAP/ui5-builder/commit/1191b3d4fac9ab7b78467d254afa88041962c416) + +### Dependency Updates +- Bump terser from 4.8.0 to 5.2.1 ([#511](https://github.com/SAP/ui5-builder/issues/511)) [`18f0df8`](https://github.com/SAP/ui5-builder/commit/18f0df84d7f3f4c7de9b1cacf06a5f5d2f0de8a9) + + + +## [v2.2.0] - 2020-09-02 +### Bug Fixes +- SapUiDefine call should not fail when there's no factory function ([#491](https://github.com/SAP/ui5-builder/issues/491)) [`25c6a3c`](https://github.com/SAP/ui5-builder/commit/25c6a3c9cae0d41f2757a8f0641bc043e171201b) + +### Features +- Add generateResourcesJson task ([#390](https://github.com/SAP/ui5-builder/issues/390)) [`021f439`](https://github.com/SAP/ui5-builder/commit/021f439e4125403d0d9e2fa0b7bcd3174ceb46e6) + + + +## [v2.1.0] - 2020-08-11 +### Features +- Implement TaskUtil class [`a7074ae`](https://github.com/SAP/ui5-builder/commit/a7074aeb8167330fd1b6d30bf5b387a212cd6b1b) +- **generateFlexChangesBundle:** Hide bundle input from build result [`001183a`](https://github.com/SAP/ui5-builder/commit/001183a4981bb5fe43039cedfbea70c2090b24db) + + + +## [v2.0.7] - 2020-08-10 +### Bug Fixes +- **generateLibraryPreload:** Ignore missing modules ([#481](https://github.com/SAP/ui5-builder/issues/481)) [`97b339f`](https://github.com/SAP/ui5-builder/commit/97b339f9c5dbddc8b80ed11c68f557d4eddc7f0a) + +### Dependency Updates +- Pin estraverse to v5.1.0 [`e5bc455`](https://github.com/SAP/ui5-builder/commit/e5bc4552015b71678102fd922609ef184502410c) + + + +## [v2.0.6] - 2020-07-21 +### Bug Fixes +- **SmartTemplateAnalyzer:** Do not throw in case missing dependency is expected ([#479](https://github.com/SAP/ui5-builder/issues/479)) [`b2150c3`](https://github.com/SAP/ui5-builder/commit/b2150c303fb14cd07b1f1ecadd1db5117cc7dccf) + + + +## [v2.0.5] - 2020-07-14 +### Bug Fixes +- **Node.js API:** TypeScript type definition support ([#475](https://github.com/SAP/ui5-builder/issues/475)) [`7858810`](https://github.com/SAP/ui5-builder/commit/785881061fe72e25230573ffb6b2a440d6782792) +- **XMLTemplateAnalyzer:** Handle empty XML view/fragment ([#471](https://github.com/SAP/ui5-builder/issues/471)) [`7488d5f`](https://github.com/SAP/ui5-builder/commit/7488d5f2c9216ac87e47ac7019fbc18674e86e30) + + + +## [v2.0.4] - 2020-06-15 +### Bug Fixes +- **ComponentAnalyzer:** Properly handle sap.ui5/routing ([#463](https://github.com/SAP/ui5-builder/issues/463)) [`717f2ec`](https://github.com/SAP/ui5-builder/commit/717f2ec8e6b04e67966183d25cc0ae59db94f43b) + + + +## [v2.0.3] - 2020-05-19 +### Bug Fixes +- Align JSDoc template and scripts with OpenUI5 1.79 ([#460](https://github.com/SAP/ui5-builder/issues/460)) [`c868fa0`](https://github.com/SAP/ui5-builder/commit/c868fa0d0a4c46d6c3098785a23fee3b7097cf02) +- **manifestBundler:** Add support for i18n object configuration ([#458](https://github.com/SAP/ui5-builder/issues/458)) [`85c4e19`](https://github.com/SAP/ui5-builder/commit/85c4e1958adf407b0dc2f7d4b324e9de354ab670) + + + +## [v2.0.2] - 2020-05-14 + + +## [v2.0.1] - 2020-04-30 +### Bug Fixes +- Namespaces in API Reference (JSDoc) [`b2a9a10`](https://github.com/SAP/ui5-builder/commit/b2a9a10dfee0ab40ce47eb4fb28666f6ea1f2360) + + + +## [v2.0.0] - 2020-03-31 +### Breaking Changes +- Make namespace mandatory for application and library projects ([#430](https://github.com/SAP/ui5-builder/issues/430)) [`ee96c00`](https://github.com/SAP/ui5-builder/commit/ee96c00d762ce24bba39f6c947997fcbb79efaae) +- Require Node.js >= 10 [`5451765`](https://github.com/SAP/ui5-builder/commit/5451765f648ecfe2c057cc2feed2c8fb7e98ef00) +- **LibraryFormatter:** Ignore manifest.json of nested apps [`846e929`](https://github.com/SAP/ui5-builder/commit/846e9290ef29aadc1ad18203003983181cd9c23a) + +### Dependency Updates +- Bump globby from 10.0.2 to 11.0.0 ([#399](https://github.com/SAP/ui5-builder/issues/399)) [`29efbbd`](https://github.com/SAP/ui5-builder/commit/29efbbd8c5d8bf0aca19e75b08f7b3d6f89e8556) + +### Features +- **buildThemes:** Add filtering for available themes ([#419](https://github.com/SAP/ui5-builder/issues/419)) [`848c503`](https://github.com/SAP/ui5-builder/commit/848c5032e98d229a655ddd17f07e252b57838f29) + +### BREAKING CHANGE + +If a library contains both, a manifest.json and .library file, they must +either be located in the same directory or the manifest.json is ignored. +In cases where the manifest.json is located on a higher level or +different directory on the same level than a .library file, an exception +is thrown. + +UI5 Project must be able to determine the project's namespace, +otherwise an error is thrown. + +Support for older Node.js releases has been dropped. +Only Node.js v10 or higher is supported. + + + +## [v1.10.1] - 2020-02-24 +### Bug Fixes +- **ApplicationBuilder:** Fix pattern to glob for .library files [`032d9a9`](https://github.com/SAP/ui5-builder/commit/032d9a974373ffc504fc65b46befe523eb3e4c7d) + + + +## [v1.10.0] - 2020-02-10 +### Bug Fixes +- Ensure proper handling of multi-byte characters in streams ([#411](https://github.com/SAP/ui5-builder/issues/411)) [`e906ec0`](https://github.com/SAP/ui5-builder/commit/e906ec0c3c3eb9fef874f2b7666c692915a496c6) +- **Bundling:** Dynamic preload calls should not emit warnings [`4d22b37`](https://github.com/SAP/ui5-builder/commit/4d22b37852ec130fb3198476e4a6225a47e2b657) + +### Features +- Add experimental CSS variables and skeleton build ([#393](https://github.com/SAP/ui5-builder/issues/393)) [`df8c39b`](https://github.com/SAP/ui5-builder/commit/df8c39b3f5a69086662b6f92c32e1364c1a93903) + + + +## [v1.9.0] - 2020-01-13 +### Bug Fixes +- Use 'defaultFileTypes' from bundle configuration ([#385](https://github.com/SAP/ui5-builder/issues/385)) [`c21e13e`](https://github.com/SAP/ui5-builder/commit/c21e13ea2d7f629b1f91b9acf625989f396c6b4f) +- Detect dynamic dependencies also when newer APIs are used ([#391](https://github.com/SAP/ui5-builder/issues/391)) [`ed1cc9d`](https://github.com/SAP/ui5-builder/commit/ed1cc9d45e517b3b38815483cc60fa7182ffd067) + +### Features +- Add new theme-library type ([#285](https://github.com/SAP/ui5-builder/issues/285)) [`a59287b`](https://github.com/SAP/ui5-builder/commit/a59287b670e956ef29ffe10bbbe1c3506ea3b330) +- **AbstractBuilder:** Allow adding custom tasks for types that have no standard tasks [`654450d`](https://github.com/SAP/ui5-builder/commit/654450df07c22bd1930c014f8b3d6904df8248e9) + + + +## [v1.8.0] - 2019-12-16 +### Features +- Add included/excludedDependencies parameter ([#380](https://github.com/SAP/ui5-builder/issues/380)) [`d6ac24a`](https://github.com/SAP/ui5-builder/commit/d6ac24ab76445568afab3fce9c813a0d5c4c4331) + + + +## [v1.7.1] - 2019-11-18 +### Dependency Updates +- Bump less-openui5 from 0.7.0 to 0.8.0 [`11101d4`](https://github.com/SAP/ui5-builder/commit/11101d4090718f6bee9f6b4851e05b1e1f33d57b) + + + +## [v1.7.0] - 2019-11-07 +### Bug Fixes +- **JSDoc:** Use the rel="noopener" attribute for external links. ([#361](https://github.com/SAP/ui5-builder/issues/361)) [`c702104`](https://github.com/SAP/ui5-builder/commit/c7021046af2ac66aaef8db3841192da8a254d304) + +### Dependency Updates +- Bump less-openui5 from 0.6.0 to 0.7.0 [`fdb0241`](https://github.com/SAP/ui5-builder/commit/fdb0241faec60062b1da52cc296dc343507fb802) + +### Features +- **buildThemes:** Add "compress" option ([#363](https://github.com/SAP/ui5-builder/issues/363)) [`3a0cf6a`](https://github.com/SAP/ui5-builder/commit/3a0cf6aa990a48830d3c22dac285036a290534d8) +- **flexChangesBundler:** Add flexibility-bundle.json ([#353](https://github.com/SAP/ui5-builder/issues/353)) [`cecc97d`](https://github.com/SAP/ui5-builder/commit/cecc97dd626268da2d2c707c5e0a6fabbfc561b6) + + + +## [v1.6.1] - 2019-10-24 +### Bug Fixes +- **jsdoc:** Adopt version range to micro releases ([#357](https://github.com/SAP/ui5-builder/issues/357)) [`619b959`](https://github.com/SAP/ui5-builder/commit/619b959d93441fef1be8c1609ebe5a9eb15759f5) + + + +## [v1.6.0] - 2019-10-24 +### Bug Fixes +- Update JSDoc to 3.6.3 ([#346](https://github.com/SAP/ui5-builder/issues/346)) [`78e2a22`](https://github.com/SAP/ui5-builder/commit/78e2a229f2ae11ca37538a75ac6746ff92af7b84) + +### Features +- **Simple Build Extensibility:** Pass project namespace to custom tasks [`1a167c5`](https://github.com/SAP/ui5-builder/commit/1a167c560ed8cc4e2c28a6c11efb1bf5ed142be9) + + + +## [v1.5.3] - 2019-10-11 +### Bug Fixes +- **Bundling:** merge dependency analysis results with raw module infos ([#340](https://github.com/SAP/ui5-builder/issues/340)) [`af4318a`](https://github.com/SAP/ui5-builder/commit/af4318a75d742bbd2e5566d2ffde2bc5a823ef06) + + + +## [v1.5.2] - 2019-10-09 +### Bug Fixes +- Improve recognition of main module in case of bundles ([#341](https://github.com/SAP/ui5-builder/issues/341)) [`7a560b4`](https://github.com/SAP/ui5-builder/commit/7a560b4bbc4c862ebded6f9e9f12c2156b1e33d1) +- Align set of known file types with runtime ([#337](https://github.com/SAP/ui5-builder/issues/337)) [`8b372f1`](https://github.com/SAP/ui5-builder/commit/8b372f1ad65d0edfe5cd440bd9352db7e48ea156) +- **manifestCreator:** Only consider component files called Component.js ([#273](https://github.com/SAP/ui5-builder/issues/273)) [`82fe267`](https://github.com/SAP/ui5-builder/commit/82fe2675114c13603238889e43be498f92d22a51) + + + +## [v1.5.1] - 2019-09-04 +### Bug Fixes +- **XMLTemplateAnalyzer:** Throws on tags without attributes ([#322](https://github.com/SAP/ui5-builder/issues/322)) [`b7f3795`](https://github.com/SAP/ui5-builder/commit/b7f379580d92e2d105edfc14e8feceab853f9a11) + + + +## [v1.5.0] - 2019-09-02 +### Features +- **XMLTemplateAnalyzer:** Support core:require ([#304](https://github.com/SAP/ui5-builder/issues/304)) [`b01fd85`](https://github.com/SAP/ui5-builder/commit/b01fd8538fafd33a4fc6303c58afe039d5ca1341) + + + +## [v1.4.2] - 2019-08-28 +### Bug Fixes +- Add 'sap.ui.fl' dependency to manifest.json ([#318](https://github.com/SAP/ui5-builder/issues/318)) [`a8edff4`](https://github.com/SAP/ui5-builder/commit/a8edff4cf63547cc1fc1d1c0ddfe958104fcb801) + + + +## [v1.4.1] - 2019-08-14 +### Bug Fixes +- Adapt to recent extension of estraverse's set of node types ([#310](https://github.com/SAP/ui5-builder/issues/310)) [`9db14e6`](https://github.com/SAP/ui5-builder/commit/9db14e6afc01c686c1187d8eefe327654e6cc3ca) + +### Features +- Switch to Terser for JavaScript minification [`fccb514`](https://github.com/SAP/ui5-builder/commit/fccb5145d05a8509d5b9c47fa4cea4b6299ca91d) +- **Theme Build:** Add compress option to minify output ([#295](https://github.com/SAP/ui5-builder/issues/295)) [`eea10ba`](https://github.com/SAP/ui5-builder/commit/eea10ba516c36be6aa3cdb2c8be990bc56f14078) +- **clean build folder:** Allows developers to clean build folder before start building a project [`04eb695`](https://github.com/SAP/ui5-builder/commit/04eb695fd493ce9bd1289933d5494178c1e679d7) + + + +## [v1.4.0] - 2019-07-29 +### Bug Fixes +- **versionInfo:** Use correct buildTimestamp format [`6d87b3e`](https://github.com/SAP/ui5-builder/commit/6d87b3e10db11a8755b4049ba82732c6ec4f776c) + +### Features +- Properties File Escaping ([#293](https://github.com/SAP/ui5-builder/issues/293)) [`9d213ce`](https://github.com/SAP/ui5-builder/commit/9d213ced942ed7832fbb7b50f9d444f441941f35) + + + +## [v1.3.3] - 2019-07-01 +### Bug Fixes +- Use consistent RegExp to detect copyright comments ([#275](https://github.com/SAP/ui5-builder/issues/275)) [`bd7aa40`](https://github.com/SAP/ui5-builder/commit/bd7aa409be340216a88ceb2607e85d951c9de58a) + +### Dependency Updates +- Bump globby from 9.2.0 to 10.0.0 [`1ea4a11`](https://github.com/SAP/ui5-builder/commit/1ea4a11e7177602b11049bb42e7c4149a0d55ff2) + + + +## [v1.3.2] - 2019-06-24 +### Bug Fixes +- **generateManifestBundle:** Only glob files from project namespace [`fc7f659`](https://github.com/SAP/ui5-builder/commit/fc7f659ab45a6828a1ab05a35dbe856a4a2b5f87) + + + +## [v1.3.1] - 2019-06-14 +### Bug Fixes +- Detect library namespace automatically ([#255](https://github.com/SAP/ui5-builder/issues/255)) [`604d4d3`](https://github.com/SAP/ui5-builder/commit/604d4d36745c9581969c411a0a78e56981948d0e) + + + +## [v1.3.0] - 2019-06-03 +### Features +- **Builder:** Add excludes option ([#254](https://github.com/SAP/ui5-builder/issues/254)) [`6a7883e`](https://github.com/SAP/ui5-builder/commit/6a7883e9c39220084660993f77c0d4c4c37ec29c) + + + +## [v1.2.3] - 2019-05-15 +### Bug Fixes +- **JSDoc:** Implement own tmp dir lifecycle [`3f85abf`](https://github.com/SAP/ui5-builder/commit/3f85abfe9bf05e008c43cf6489d26ecb0b7d8ee3) + + + +## [v1.2.2] - 2019-05-08 +### Bug Fixes +- **package.json:** Fix JSDoc version to 3.5.5 [`873469d`](https://github.com/SAP/ui5-builder/commit/873469d0d9295a7d7d5775f446c170068d086502) + + + +## [v1.2.1] - 2019-05-07 +### Bug Fixes +- **SmartTemplateAnalyzer:** Detect dependencies from "pages" object [`2d400c2`](https://github.com/SAP/ui5-builder/commit/2d400c2ac0883ad57b4aa894c46a0dd5aecb070a) + + + +## [v1.2.0] - 2019-04-25 +### Features +- Add option to use hash signatures in cachebuster info file [`a4e8338`](https://github.com/SAP/ui5-builder/commit/a4e83383c7371cdde8573a901fdadd2ab243440e) + + + +## [v1.1.1] - 2019-04-24 +### Bug Fixes +- **ApplicationFormatter:** detect the namespace for Maven placeholders ([#243](https://github.com/SAP/ui5-builder/issues/243)) [`49ecb07`](https://github.com/SAP/ui5-builder/commit/49ecb07f41efdf0778f04b05117e0daae01e8710) + + + +## [v1.1.0] - 2019-04-12 +### Features +- Build the manifest-bundle.zip for applications and libraries [`f53aeea`](https://github.com/SAP/ui5-builder/commit/f53aeea594071616974d0e14b6d41609603bbd5b) +- Generate the AppCacheBuster index file for apps [`dd653c8`](https://github.com/SAP/ui5-builder/commit/dd653c8f3883da41f5723093d7e40aeb3258c180) + + + +## [v1.0.5] - 2019-04-03 +### Bug Fixes +- Generate sap-ui-custom-dbg.js for self-contained build ([#234](https://github.com/SAP/ui5-builder/issues/234)) [`d769d98`](https://github.com/SAP/ui5-builder/commit/d769d9894fe0a9d5262aea2cde86b463bc55433d) +- Add bundling sap-ui-core-noJQuery.js and sap-ui-core-noJQuery-dbg.js ([#235](https://github.com/SAP/ui5-builder/issues/235)) [`e7a7a63`](https://github.com/SAP/ui5-builder/commit/e7a7a63983dec54f53ac1c906eb2f970948db25d) + + + +## [v1.0.4] - 2019-03-27 +### Dependency Updates +- Bump tmp from 0.0.33 to 0.1.0 ([#220](https://github.com/SAP/ui5-builder/issues/220)) [`4fa642c`](https://github.com/SAP/ui5-builder/commit/4fa642c460f71b48ff690e3dc09de8cb0decca4e) + + + +## [v1.0.3] - 2019-03-21 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 1.0.1 to 1.0.2 ([#214](https://github.com/SAP/ui5-builder/issues/214)) [`eb85e0a`](https://github.com/SAP/ui5-builder/commit/eb85e0afa1e5e82571312448ce8ab7ef87a7bcbc) +- Bump [@ui5](https://github.com/ui5)/logger from 1.0.0 to 1.0.1 ([#212](https://github.com/SAP/ui5-builder/issues/212)) [`20557e8`](https://github.com/SAP/ui5-builder/commit/20557e85ac0de835b5d5ff455d613d102521d3c7) + +### Features +- Add JSDoc build functionalities ([#42](https://github.com/SAP/ui5-builder/issues/42)) [`293a4b0`](https://github.com/SAP/ui5-builder/commit/293a4b0ae44706490fb568be69d4032150a2891a) + + + +## [v1.0.2] - 2019-02-28 +### Bug Fixes +- Warning log of duplicate module declaration [`9a790a3`](https://github.com/SAP/ui5-builder/commit/9a790a30905cdebe6ba3db283b75983135b967d6) +- **Bundler:** Create sap-ui-core-dbg.js ([#176](https://github.com/SAP/ui5-builder/issues/176)) [`feb95e4`](https://github.com/SAP/ui5-builder/commit/feb95e41c199d5b455272ba5886cdd79d1502cd7) +- **ComponentAnalyzer:** Detect model types from dataSource [`efc5cef`](https://github.com/SAP/ui5-builder/commit/efc5cef5fb2988e78dfd1ea26f3c6ba818c69d87) + + + +## [v1.0.1] - 2019-02-01 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 1.0.0 to 1.0.1 [`55ab125`](https://github.com/SAP/ui5-builder/commit/55ab125e60c138a5a419cae1064590e5e535d893) + + + +## [v1.0.0] - 2019-02-01 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 0.2.0 to 1.0.0 ([#142](https://github.com/SAP/ui5-builder/issues/142)) [`2c6893f`](https://github.com/SAP/ui5-builder/commit/2c6893f9029c161e95a4078caeb7e9f3a22a3af2) + +### Features +- Add transformation of apps index.html in self-contained build ([#137](https://github.com/SAP/ui5-builder/issues/137)) [`6549b8a`](https://github.com/SAP/ui5-builder/commit/6549b8a832cc50749159f1295bd93ef6a04733b6) + +### BREAKING CHANGE + +When running a self-contained build on an application project, the +index.html will be transformed by adopting the UI5 bootstrap script tag +to load the custom bundle file instead. + + + +## [v0.2.9] - 2019-01-03 +### Bug Fixes +- **ComponentAnalyzer:** Fully handle sap.ui5/routing ([#124](https://github.com/SAP/ui5-builder/issues/124)) [`c59b5b1`](https://github.com/SAP/ui5-builder/commit/c59b5b1efdc3a588fb8a13029a6593feab142e0c) +- **XMLTemplateAnalyzer:** Ignore properties with data binding [`0d5cf50`](https://github.com/SAP/ui5-builder/commit/0d5cf5086566dd0609fa354a5822f6538b335065) + + + +## [v0.2.8] - 2018-12-19 +### Bug Fixes +- Themes not beeing build [`de26564`](https://github.com/SAP/ui5-builder/commit/de26564c2c3af3376ccf179c972eae4f0e5eeeee) + + + +## [v0.2.7] - 2018-12-19 +### Bug Fixes +- **ComponentAnalyzer:** Handle sap.ui5/rootView with type string [`469e558`](https://github.com/SAP/ui5-builder/commit/469e558cae43d6a0c063170dd23e2337c0e5af26) +- **generateLibraryPreload:** Fix sap-ui-core.js bootstrap [`7a266fd`](https://github.com/SAP/ui5-builder/commit/7a266fd48d6452ce7f6180b026109d47caf195ec) + + + +## [v0.2.6] - 2018-12-06 + + +## [v0.2.5] - 2018-11-16 +### Features +- **Builder:** Add handling for custom task configurations [`9b4ae00`](https://github.com/SAP/ui5-builder/commit/9b4ae00f62da1f5bb94aeb8a86711c2a2e98da20) + + + +## [v0.2.4] - 2018-10-29 +### Features +- Add module type [`d7efb8a`](https://github.com/SAP/ui5-builder/commit/d7efb8a16334571e7997daccd4f69e1e06591c25) + + + +## [v0.2.3] - 2018-10-09 +### Bug Fixes +- Replace copyright and version strings in *.json and .library files [`f305429`](https://github.com/SAP/ui5-builder/commit/f305429067610404f0958b55ef3a570e555a532e) +- **generateLibraryPreload:** Add new sap.ui.core library namespaces [`ea901a7`](https://github.com/SAP/ui5-builder/commit/ea901a78c27e5fd112f9ac761e621b7f1c474f07) + + + +## [v0.2.2] - 2018-10-05 +### Bug Fixes +- **processors/versionInfoGenerator:** Remove "gav" property [`2bf41e1`](https://github.com/SAP/ui5-builder/commit/2bf41e1622df70818f925aabafe16de082fa3884) + +### Features +- **BundleBuilder:** support modules using ES6 with usePredefineCalls ([#67](https://github.com/SAP/ui5-builder/issues/67)) [`d1a4f1f`](https://github.com/SAP/ui5-builder/commit/d1a4f1f39e4262eafa8df1548f0e944998fd00a3) + + + +## [v0.2.1] - 2018-07-17 +### Bug Fixes +- **generateLibraryManifest:** i18n/css handling [`4e52a96`](https://github.com/SAP/ui5-builder/commit/4e52a9654b28a7646597ce0e0f010589ff7905d5) + + + +## [v0.2.0] - 2018-07-11 +### Bug Fixes +- Close gaps in routing support of ComponentAnalyzer ([#46](https://github.com/SAP/ui5-builder/issues/46)) [`4697531`](https://github.com/SAP/ui5-builder/commit/4697531cbafebf881e78b80e78d098d1361fe9a5) + + + +## [v0.1.1] - 2018-07-02 +### Bug Fixes +- iterate over routes using a for loop if it is an object ([#31](https://github.com/SAP/ui5-builder/issues/31)) [`e9823f6`](https://github.com/SAP/ui5-builder/commit/e9823f68cf038b5fde172916e483a01d5eb88f1f) + + + +## [v0.1.0] - 2018-06-26 +### Bug Fixes +- Bundles should be built one after another [`164ba32`](https://github.com/SAP/ui5-builder/commit/164ba328c6e172297d71b9d3ef871005931cca71) +- reduce build time at the price of a slightly increased code size ([#37](https://github.com/SAP/ui5-builder/issues/37)) [`1fb8d00`](https://github.com/SAP/ui5-builder/commit/1fb8d0049235467fcbd40f53e725cc419a8bc730) +- Use the target bundle format to decide decoration ([#24](https://github.com/SAP/ui5-builder/issues/24)) [`83703bc`](https://github.com/SAP/ui5-builder/commit/83703bca17fd18b9ac700fae4801d87a4d86961d) + + + +## [v0.0.2] - 2018-06-21 +### Bug Fixes +- **Builders:** Do not bundle debug files [`19800a1`](https://github.com/SAP/ui5-builder/commit/19800a16689210c13495bc1bd0949896500cfc52) + + + +## v0.0.1 - 2018-06-06 +### Bug Fixes +- Restore default component preload [`a09bec2`](https://github.com/SAP/ui5-builder/commit/a09bec2f57f45a1c5d74681b3bdec4f7fdc45343) + +### Features +- Add ability to configure component preloads and custom bundles [`2241e5f`](https://github.com/SAP/ui5-builder/commit/2241e5ff98fd95f1f80cc74959655ae7a9c660e7) + + +{{- if .Versions }} +{{ range .Versions -}} +{{ if and .Tag.Previous (ne .Tag.Name "v3.0.0") -}} +[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} +{{ end -}} +[v3.5.1]: https://github.com/SAP/ui5-builder/compare/v3.5.0...v3.5.1 +[v3.5.0]: https://github.com/SAP/ui5-builder/compare/v3.4.1...v3.5.0 +[v3.4.1]: https://github.com/SAP/ui5-builder/compare/v3.4.0...v3.4.1 +[v3.4.0]: https://github.com/SAP/ui5-builder/compare/v3.3.1...v3.4.0 +[v3.3.1]: https://github.com/SAP/ui5-builder/compare/v3.3.0...v3.3.1 +[v3.3.0]: https://github.com/SAP/ui5-builder/compare/v3.2.0...v3.3.0 +[v3.2.0]: https://github.com/SAP/ui5-builder/compare/v3.1.1...v3.2.0 +[v3.1.1]: https://github.com/SAP/ui5-builder/compare/v3.1.0...v3.1.1 +[v3.1.0]: https://github.com/SAP/ui5-builder/compare/v3.0.9...v3.1.0 +[v3.0.9]: https://github.com/SAP/ui5-builder/compare/v3.0.8...v3.0.9 +[v3.0.8]: https://github.com/SAP/ui5-builder/compare/v3.0.7...v3.0.8 +[v3.0.7]: https://github.com/SAP/ui5-builder/compare/v3.0.6...v3.0.7 +[v3.0.6]: https://github.com/SAP/ui5-builder/compare/v3.0.5...v3.0.6 +[v3.0.5]: https://github.com/SAP/ui5-builder/compare/v3.0.4...v3.0.5 +[v3.0.4]: https://github.com/SAP/ui5-builder/compare/v3.0.3...v3.0.4 +[v3.0.3]: https://github.com/SAP/ui5-builder/compare/v3.0.2...v3.0.3 +[v3.0.2]: https://github.com/SAP/ui5-builder/compare/v3.0.1...v3.0.2 +[v3.0.1]: https://github.com/SAP/ui5-builder/compare/v3.0.0...v3.0.1 +[v3.0.0]: https://github.com/SAP/ui5-builder/compare/v2.11.2...v3.0.0 +[v2.11.9]: https://github.com/SAP/ui5-builder/compare/v2.11.8...v2.11.9 +[v2.11.8]: https://github.com/SAP/ui5-builder/compare/v2.11.7...v2.11.8 +[v2.11.7]: https://github.com/SAP/ui5-builder/compare/v2.11.6...v2.11.7 +[v2.11.6]: https://github.com/SAP/ui5-builder/compare/v2.11.5...v2.11.6 +[v2.11.5]: https://github.com/SAP/ui5-builder/compare/v2.11.4...v2.11.5 +[v2.11.4]: https://github.com/SAP/ui5-builder/compare/v2.11.3...v2.11.4 +[v2.11.3]: https://github.com/SAP/ui5-builder/compare/v2.11.2...v2.11.3 +[v2.11.2]: https://github.com/SAP/ui5-builder/compare/v2.11.1...v2.11.2 +[v2.11.1]: https://github.com/SAP/ui5-builder/compare/v2.11.0...v2.11.1 +[v2.11.0]: https://github.com/SAP/ui5-builder/compare/v2.10.0...v2.11.0 +[v2.10.0]: https://github.com/SAP/ui5-builder/compare/v2.9.5...v2.10.0 +[v2.9.5]: https://github.com/SAP/ui5-builder/compare/v2.9.4...v2.9.5 +[v2.9.4]: https://github.com/SAP/ui5-builder/compare/v2.9.3...v2.9.4 +[v2.9.3]: https://github.com/SAP/ui5-builder/compare/v2.9.2...v2.9.3 +[v2.9.2]: https://github.com/SAP/ui5-builder/compare/v2.9.1...v2.9.2 +[v2.9.1]: https://github.com/SAP/ui5-builder/compare/v2.9.0...v2.9.1 +[v2.9.0]: https://github.com/SAP/ui5-builder/compare/v2.8.4...v2.9.0 +[v2.8.4]: https://github.com/SAP/ui5-builder/compare/v2.8.3...v2.8.4 +[v2.8.3]: https://github.com/SAP/ui5-builder/compare/v2.8.2...v2.8.3 +[v2.8.2]: https://github.com/SAP/ui5-builder/compare/v2.8.1...v2.8.2 +[v2.8.1]: https://github.com/SAP/ui5-builder/compare/v2.8.0...v2.8.1 +[v2.8.0]: https://github.com/SAP/ui5-builder/compare/v2.7.2...v2.8.0 +[v2.7.2]: https://github.com/SAP/ui5-builder/compare/v2.7.1...v2.7.2 +[v2.7.1]: https://github.com/SAP/ui5-builder/compare/v2.7.0...v2.7.1 +[v2.7.0]: https://github.com/SAP/ui5-builder/compare/v2.6.1...v2.7.0 +[v2.6.1]: https://github.com/SAP/ui5-builder/compare/v2.6.0...v2.6.1 +[v2.6.0]: https://github.com/SAP/ui5-builder/compare/v2.5.1...v2.6.0 +[v2.5.1]: https://github.com/SAP/ui5-builder/compare/v2.5.0...v2.5.1 +[v2.5.0]: https://github.com/SAP/ui5-builder/compare/v2.4.5...v2.5.0 +[v2.4.5]: https://github.com/SAP/ui5-builder/compare/v2.4.4...v2.4.5 +[v2.4.4]: https://github.com/SAP/ui5-builder/compare/v2.4.3...v2.4.4 +[v2.4.3]: https://github.com/SAP/ui5-builder/compare/v2.4.2...v2.4.3 +[v2.4.2]: https://github.com/SAP/ui5-builder/compare/v2.4.1...v2.4.2 +[v2.4.1]: https://github.com/SAP/ui5-builder/compare/v2.4.0...v2.4.1 +[v2.4.0]: https://github.com/SAP/ui5-builder/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/SAP/ui5-builder/compare/v2.2.1...v2.3.0 +[v2.2.1]: https://github.com/SAP/ui5-builder/compare/v2.2.0...v2.2.1 +[v2.2.0]: https://github.com/SAP/ui5-builder/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/SAP/ui5-builder/compare/v2.0.7...v2.1.0 +[v2.0.7]: https://github.com/SAP/ui5-builder/compare/v2.0.6...v2.0.7 +[v2.0.6]: https://github.com/SAP/ui5-builder/compare/v2.0.5...v2.0.6 +[v2.0.5]: https://github.com/SAP/ui5-builder/compare/v2.0.4...v2.0.5 +[v2.0.4]: https://github.com/SAP/ui5-builder/compare/v2.0.3...v2.0.4 +[v2.0.3]: https://github.com/SAP/ui5-builder/compare/v2.0.2...v2.0.3 +[v2.0.2]: https://github.com/SAP/ui5-builder/compare/v2.0.1...v2.0.2 +[v2.0.1]: https://github.com/SAP/ui5-builder/compare/v2.0.0...v2.0.1 +[v2.0.0]: https://github.com/SAP/ui5-builder/compare/v1.10.1...v2.0.0 +[v1.10.1]: https://github.com/SAP/ui5-builder/compare/v1.10.0...v1.10.1 +[v1.10.0]: https://github.com/SAP/ui5-builder/compare/v1.9.0...v1.10.0 +[v1.9.0]: https://github.com/SAP/ui5-builder/compare/v1.8.0...v1.9.0 +[v1.8.0]: https://github.com/SAP/ui5-builder/compare/v1.7.1...v1.8.0 +[v1.7.1]: https://github.com/SAP/ui5-builder/compare/v1.7.0...v1.7.1 +[v1.7.0]: https://github.com/SAP/ui5-builder/compare/v1.6.1...v1.7.0 +[v1.6.1]: https://github.com/SAP/ui5-builder/compare/v1.6.0...v1.6.1 +[v1.6.0]: https://github.com/SAP/ui5-builder/compare/v1.5.3...v1.6.0 +[v1.5.3]: https://github.com/SAP/ui5-builder/compare/v1.5.2...v1.5.3 +[v1.5.2]: https://github.com/SAP/ui5-builder/compare/v1.5.1...v1.5.2 +[v1.5.1]: https://github.com/SAP/ui5-builder/compare/v1.5.0...v1.5.1 +[v1.5.0]: https://github.com/SAP/ui5-builder/compare/v1.4.2...v1.5.0 +[v1.4.2]: https://github.com/SAP/ui5-builder/compare/v1.4.1...v1.4.2 +[v1.4.1]: https://github.com/SAP/ui5-builder/compare/v1.4.0...v1.4.1 +[v1.4.0]: https://github.com/SAP/ui5-builder/compare/v1.3.3...v1.4.0 +[v1.3.3]: https://github.com/SAP/ui5-builder/compare/v1.3.2...v1.3.3 +[v1.3.2]: https://github.com/SAP/ui5-builder/compare/v1.3.1...v1.3.2 +[v1.3.1]: https://github.com/SAP/ui5-builder/compare/v1.3.0...v1.3.1 +[v1.3.0]: https://github.com/SAP/ui5-builder/compare/v1.2.3...v1.3.0 +[v1.2.3]: https://github.com/SAP/ui5-builder/compare/v1.2.2...v1.2.3 +[v1.2.2]: https://github.com/SAP/ui5-builder/compare/v1.2.1...v1.2.2 +[v1.2.1]: https://github.com/SAP/ui5-builder/compare/v1.2.0...v1.2.1 +[v1.2.0]: https://github.com/SAP/ui5-builder/compare/v1.1.1...v1.2.0 +[v1.1.1]: https://github.com/SAP/ui5-builder/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/SAP/ui5-builder/compare/v1.0.5...v1.1.0 +[v1.0.5]: https://github.com/SAP/ui5-builder/compare/v1.0.4...v1.0.5 +[v1.0.4]: https://github.com/SAP/ui5-builder/compare/v1.0.3...v1.0.4 +[v1.0.3]: https://github.com/SAP/ui5-builder/compare/v1.0.2...v1.0.3 +[v1.0.2]: https://github.com/SAP/ui5-builder/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/SAP/ui5-builder/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/SAP/ui5-builder/compare/v0.2.9...v1.0.0 +[v0.2.9]: https://github.com/SAP/ui5-builder/compare/v0.2.8...v0.2.9 +[v0.2.8]: https://github.com/SAP/ui5-builder/compare/v0.2.7...v0.2.8 +[v0.2.7]: https://github.com/SAP/ui5-builder/compare/v0.2.6...v0.2.7 +[v0.2.6]: https://github.com/SAP/ui5-builder/compare/v0.2.5...v0.2.6 +[v0.2.5]: https://github.com/SAP/ui5-builder/compare/v0.2.4...v0.2.5 +[v0.2.4]: https://github.com/SAP/ui5-builder/compare/v0.2.3...v0.2.4 +[v0.2.3]: https://github.com/SAP/ui5-builder/compare/v0.2.2...v0.2.3 +[v0.2.2]: https://github.com/SAP/ui5-builder/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/SAP/ui5-builder/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/SAP/ui5-builder/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/SAP/ui5-builder/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/SAP/ui5-builder/compare/v0.0.2...v0.1.0 +[v0.0.2]: https://github.com/SAP/ui5-builder/compare/v0.0.1...v0.0.2 diff --git a/packages/builder/.chglog/RELEASE.tpl.md b/packages/builder/.chglog/RELEASE.tpl.md new file mode 100755 index 00000000000..efc482c385d --- /dev/null +++ b/packages/builder/.chglog/RELEASE.tpl.md @@ -0,0 +1,33 @@ +{{ range .Versions }} +{{ range .CommitGroups -}} +### {{ .Title }} +{{ range .Commits -}} +- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} [`{{ .Hash.Short }}`]({{ $.Info.RepositoryURL }}/commit/{{ .Hash.Long }}) +{{ end }} +{{ end -}} + +{{- if .RevertCommits -}} +### Reverts +{{ range .RevertCommits -}} +- {{ .Revert.Header }} +{{ end }} +{{ end -}} + +{{- if .NoteGroups -}} +{{ range .NoteGroups -}} +### {{ .Title }} +{{ range .Notes }} +{{ .Body }} +{{ end }} +{{ end -}} +{{ end -}} + +{{ if .Tag.Previous }} +### All changes +[`{{ .Tag.Previous.Name }}...{{ .Tag.Name }}`] +{{ end }} + +{{ if .Tag.Previous -}} +[`{{ .Tag.Previous.Name }}...{{ .Tag.Name }}`]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} +{{ end -}} +{{ end -}} diff --git a/packages/builder/.chglog/config.yml b/packages/builder/.chglog/config.yml new file mode 100755 index 00000000000..b3601636fae --- /dev/null +++ b/packages/builder/.chglog/config.yml @@ -0,0 +1,33 @@ +style: github +template: CHANGELOG.tpl.md +info: + title: CHANGELOG + repository_url: https://github.com/SAP/ui5-builder +options: + commits: + filters: + Type: + - FEATURE + - FIX + - PERF + - DEPENDENCY + - BREAKING + commit_groups: + title_maps: + FEATURE: Features + FIX: Bug Fixes + PERF: Performance Improvements + DEPENDENCY: Dependency Updates + BREAKING: Breaking Changes + header: + pattern: "^\\[(\\w*)\\]\\s(?:([^\\:]*)\\:\\s)?(.*)$" + pattern_maps: + - Type + - Scope + - Subject + issues: + prefix: + - "#" + notes: + keywords: + - BREAKING CHANGE diff --git a/packages/builder/.chglog/release-config.yml b/packages/builder/.chglog/release-config.yml new file mode 100755 index 00000000000..e65dadc477a --- /dev/null +++ b/packages/builder/.chglog/release-config.yml @@ -0,0 +1,33 @@ +style: github +template: RELEASE.tpl.md +info: + repository_url: https://github.com/SAP/ui5-builder +options: + tag_filter_pattern: '^v[^0123]' # For release notes ignore versions below v4 to that we always compare the _last v4+_ tag with the current release + commits: + filters: + Type: + - FEATURE + - FIX + - PERF + - DEPENDENCY + - BREAKING + commit_groups: + title_maps: + FEATURE: Features + FIX: Bug Fixes + PERF: Performance Improvements + DEPENDENCY: Dependency Updates + BREAKING: Breaking Changes + header: + pattern: "^\\[(\\w*)\\]\\s(?:([^\\:]*)\\:\\s)?(.*)$" + pattern_maps: + - Type + - Scope + - Subject + issues: + prefix: + - "#" + notes: + keywords: + - BREAKING CHANGE diff --git a/packages/builder/.editorconfig b/packages/builder/.editorconfig new file mode 100644 index 00000000000..b432804f7fc --- /dev/null +++ b/packages/builder/.editorconfig @@ -0,0 +1,20 @@ +# see http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = tab + +[*.{css,html,js,cjs,mjs,jsx,ts,tsx,less,txt,json,yml,md}] +trim_trailing_whitespace = true +end_of_line = lf +indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/builder/.gitattributes b/packages/builder/.gitattributes new file mode 100644 index 00000000000..6313b56c578 --- /dev/null +++ b/packages/builder/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/packages/builder/.github/ISSUE_TEMPLATE.md b/packages/builder/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..6ddeb2221a8 --- /dev/null +++ b/packages/builder/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,3 @@ +## 🚨 Issues Have Been Transferred to UI5 CLI Repository + +Please create new issues in the UI5 CLI repository: https://github.com/UI5/cli/issues/new/choose diff --git a/packages/builder/.github/ISSUE_TEMPLATE/config.yml b/packages/builder/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..34df09809c9 --- /dev/null +++ b/packages/builder/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Report UI5 CLI Issues or Request a Feature + url: https://github.com/UI5/cli/issues/new/choose + about: Please create new issues in the UI5 CLI repository diff --git a/packages/builder/.github/PULL_REQUEST_TEMPLATE.md b/packages/builder/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..2f043b5c5cc --- /dev/null +++ b/packages/builder/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +**Thank you for your contribution!** 🙌 + +To get it merged faster, kindly review the checklist below: + +## Pull Request Checklist +- [ ] Reviewed the [Contributing Guidelines](https://github.com/UI5/cli/blob/main/CONTRIBUTING.md#-contributing-code) + + Especially the [How to Contribute](https://github.com/UI5/cli/blob/main/CONTRIBUTING.md#how-to-contribute) section +- [ ] [No merge commits](https://github.com/UI5/cli/blob/main/docs/Guidelines.md#no-merge-commits) +- [ ] [Correct commit message style](https://github.com/UI5/cli/blob/main/docs/Guidelines.md#commit-message-style) diff --git a/packages/builder/.github/dependabot.yml b/packages/builder/.github/dependabot.yml new file mode 100644 index 00000000000..ebf6ca750f8 --- /dev/null +++ b/packages/builder/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" +- package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + day: sunday + time: "10:00" + timezone: Etc/UCT + reviewers: + - "SAP/ui5-foundation" + versioning-strategy: increase + commit-message: + prefix: "[DEPENDENCY] " + prefix-development: "[INTERNAL] " diff --git a/packages/builder/.github/in-solidarity.yml b/packages/builder/.github/in-solidarity.yml new file mode 100644 index 00000000000..4ce829a6be3 --- /dev/null +++ b/packages/builder/.github/in-solidarity.yml @@ -0,0 +1 @@ +_extends: ietf/terminology diff --git a/packages/builder/.github/workflows/dependabot-auto-merge.yml b/packages/builder/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000000..43d92c94fd6 --- /dev/null +++ b/packages/builder/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,28 @@ +name: Dependabot auto-merge +on: + pull_request: + branches: + - v4 + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' && github.event.pull_request.auto_merge == null }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve and auto-merge PRs for minor/patch updates of github-actions + if: | + steps.metadata.outputs.package-ecosystem == 'github_actions' && + contains(fromJSON('["version-update:semver-minor", "version-update:semver-patch"]'), steps.metadata.outputs.update-type) + run: gh pr review --approve "$PR_URL" && gh pr merge --auto --rebase "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/packages/builder/.github/workflows/github-ci.yml b/packages/builder/.github/workflows/github-ci.yml new file mode 100644 index 00000000000..ace9ddfa6aa --- /dev/null +++ b/packages/builder/.github/workflows/github-ci.yml @@ -0,0 +1,34 @@ +name: GitHub CI + +on: + push: + branches: + - v4 + pull_request: + branches: + - v4 + +# No permissions are required for this workflow +permissions: {} + +jobs: + test: + name: General checks, tests and coverage reporting + runs-on: ubuntu-24.04 + steps: + + - uses: actions/checkout@v4 + + - name: Use Node.js LTS 20.11.0 + uses: actions/setup-node@v4.4.0 + with: + node-version: 20.11.0 + + - name: Install dependencies + run: npm ci + + - name: Perform checks and tests + run: npm test + + - name: Send report to Coveralls + uses: coverallsapp/github-action@v2.3.6 diff --git a/packages/builder/.github/workflows/reuse-compliance.yml b/packages/builder/.github/workflows/reuse-compliance.yml new file mode 100644 index 00000000000..3dbdbb22ff9 --- /dev/null +++ b/packages/builder/.github/workflows/reuse-compliance.yml @@ -0,0 +1,21 @@ +name: REUSE + +on: + push: + branches: + - v4 + pull_request: + branches: + - v4 + +# No permissions are required for this workflow +permissions: {} + +jobs: + compliance-check: + name: Compliance Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Execute REUSE Compliance Check + uses: fsfe/reuse-action@v5 diff --git a/packages/builder/.gitignore b/packages/builder/.gitignore new file mode 100644 index 00000000000..052409d713d --- /dev/null +++ b/packages/builder/.gitignore @@ -0,0 +1,62 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# CI (Azure Pipelines) xUnit test results +test-results.xml + +# IDEs +.vscode/ +*.~vsdx +.idea/ + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# Misc +yarn.lock +.DS_Store + +# Don't include private SSH key for deployment via Travis CI +deploy_key + +# Custom directories +test/tmp/ +jsdocs/ diff --git a/packages/builder/.npmrc b/packages/builder/.npmrc new file mode 100644 index 00000000000..93ec4f76ba6 --- /dev/null +++ b/packages/builder/.npmrc @@ -0,0 +1,3 @@ +# Enforce public npm registry +registry=https://registry.npmjs.org/ +lockfile-version=3 diff --git a/packages/builder/CHANGELOG.md b/packages/builder/CHANGELOG.md new file mode 100644 index 00000000000..70734eef14a --- /dev/null +++ b/packages/builder/CHANGELOG.md @@ -0,0 +1,1110 @@ +# Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +A list of unreleased changes can be found [here](https://github.com/SAP/ui5-builder/compare/v4.0.11...HEAD). + + +## [v4.0.11] - 2025-09-11 +### Bug Fixes +- Rename project to UI5 CLI [`2d0cb3a`](https://github.com/SAP/ui5-builder/commit/2d0cb3a222508facec0a4103d048f92c2fd0b41f) + + + +## [v4.0.10] - 2025-09-03 +### Bug Fixes +- Downgrade cheerio to v1.0.0 to resolve Node compatibility issues [`4dc3a6e`](https://github.com/SAP/ui5-builder/commit/4dc3a6ef3029fca50d6b3ab45ed9667c206c6435) +- **Bundler:** Include 'bundleInfo' section in multipart bundles [`e7ab31e`](https://github.com/SAP/ui5-builder/commit/e7ab31e859c5659612f0cc726e0410064f0b3174) + + + +## [v4.0.9] - 2025-08-12 +### Bug Fixes +- **minify:** Apply value of a resource's OmitFromBuildResult-tag to derived resources [`112d204`](https://github.com/SAP/ui5-builder/commit/112d204b230c973b6c209105e4e8a383fc1da35d) + + + +## [v4.0.8] - 2025-07-15 +### Bug Fixes +- **bundle/Builder:** Skip source map for empty or trivia-only files [`4763253`](https://github.com/SAP/ui5-builder/commit/4763253985ae869c541875d82d0609cb5ef28216) + +### Dependency Updates +- Bump cheerio from 1.0.0 to 1.1.0 ([#1126](https://github.com/SAP/ui5-builder/issues/1126)) [`bade2bc`](https://github.com/SAP/ui5-builder/commit/bade2bc9f8a623a332d5768dc4ce6bd0036ba4bf) +- Bump terser from 5.39.2 to 5.40.0 ([#1118](https://github.com/SAP/ui5-builder/issues/1118)) [`80a5975`](https://github.com/SAP/ui5-builder/commit/80a5975c037e7c0bf9a041c284ec4a510bf2cbfd) + + + +## [v4.0.7] - 2025-05-19 +### Bug Fixes +- **manifestEnhancer:** Only use valid files for supportedLocales ([#1080](https://github.com/SAP/ui5-builder/issues/1080)) [`a6c04d2`](https://github.com/SAP/ui5-builder/commit/a6c04d26ae964566c82e82d1be2ef6a7fd836530) + + + +## [v4.0.6] - 2025-04-29 +### Bug Fixes +- Handle legacy-free UI5 versions [`8b38316`](https://github.com/SAP/ui5-builder/commit/8b38316bae02edfe4da1876884345c040f260c10) +- **minify:** In case of error, include full resource path in the error messsage [`aadb463`](https://github.com/SAP/ui5-builder/commit/aadb4639461998b2c113d5a4962adbdb135c9762) + + + +## [v4.0.5] - 2024-12-10 + + +## [v4.0.4] - 2024-11-29 +### Dependency Updates +- Switch from "rimraf" to native "fs.rm" ([#1098](https://github.com/SAP/ui5-builder/issues/1098)) [`0adf4da`](https://github.com/SAP/ui5-builder/commit/0adf4da0e747f1fb46cf0bf0da7b3e0af8e9bcec) + + + +## [v4.0.3] - 2024-08-27 + + +## [v4.0.2] - 2024-08-20 +### Dependency Updates +- Bump cheerio from 1.0.0-rc.12 to 1.0.0 ([#1078](https://github.com/SAP/ui5-builder/issues/1078)) [`d80c79d`](https://github.com/SAP/ui5-builder/commit/d80c79d5578516cf533e649e73d55602afa8b908) + + + +## [v4.0.1] - 2024-07-29 +### Bug Fixes +- **manifestEnhancer:** Improve error handling [`16a4e78`](https://github.com/SAP/ui5-builder/commit/16a4e785ab71f8714b3926aa0bb8573f8da4dd08) +- **manifestEnhancer:** Fix fallbackLocale handling [`421a375`](https://github.com/SAP/ui5-builder/commit/421a37577c11c50d938bc59f3b8fca371a703cd3) + + + +## [v4.0.0] - 2024-07-23 +### Breaking Changes +- Drop node v21 support [`7b27171`](https://github.com/SAP/ui5-builder/commit/7b2717166686aeef8d19b1f3134ef4f39de25a80) +- Discontinue bundling of JavaScript modules as string [`7691b08`](https://github.com/SAP/ui5-builder/commit/7691b08c4cd8abc7fe3922ec913f80dca372ba4f) +- Task API arguments rename ([#995](https://github.com/SAP/ui5-builder/issues/995)) [`d1f87e0`](https://github.com/SAP/ui5-builder/commit/d1f87e0cb8b65005b034ff00b7e0d66f4a5d279e) +- Require Node.js 20.11.x/>=21.2.0 and npm >=10 [`b76ff75`](https://github.com/SAP/ui5-builder/commit/b76ff7588d65c5b5ff8fb9861a3562680e8c2e74) +- **Bundling:** Enforce usage of sap.ui.predefine instead function wrappers ([#1021](https://github.com/SAP/ui5-builder/issues/1021)) [`4bda728`](https://github.com/SAP/ui5-builder/commit/4bda7289383e8645556c68976a05db0ab5698a2b) + +### Dependency Updates +- Bump rimraf from 5.0.8 to 6.0.1 [`2b4f309`](https://github.com/SAP/ui5-builder/commit/2b4f3094107bfa7ace18091731fe2f048b233754) +- Bump terser from 5.29.1 to 5.29.2 ([#1000](https://github.com/SAP/ui5-builder/issues/1000)) [`2b6da9e`](https://github.com/SAP/ui5-builder/commit/2b6da9ed131ab21c18fdbd9a7c7328dee83bd41c) +- Bump espree from 9.6.1 to 10.0.1 [`08eb716`](https://github.com/SAP/ui5-builder/commit/08eb7165d638ccf3b078eeb3feed763765821f53) +- Bump terser from 5.27.1 to 5.27.2 ([#991](https://github.com/SAP/ui5-builder/issues/991)) [`204145d`](https://github.com/SAP/ui5-builder/commit/204145d8db2a0f76d134206b33fbef3bf1edabbf) +- Bump workerpool from 6.5.1 to 9.1.0 [`b78a70c`](https://github.com/SAP/ui5-builder/commit/b78a70cf1adec6b78d78593898942f0c7c47edca) + +### Features +- Apply default values to bunde definitions for standard tasks ([#1033](https://github.com/SAP/ui5-builder/issues/1033)) [`4178e1a`](https://github.com/SAP/ui5-builder/commit/4178e1a0315ad16b7774183a18e326f8c52f5f44) +- **Bundle 'require' section with async flag for specVersion:** 4.0 ([#1042](https://github.com/SAP/ui5-builder/issues/1042)) [`dfa67fe`](https://github.com/SAP/ui5-builder/commit/dfa67feb91b7de0d61c2713eafa639f001484d9b) +- **bundle/Builder:** Support async require sections and conditional core boot [`e421e2f`](https://github.com/SAP/ui5-builder/commit/e421e2ff7e604d9035b86d666ebaf75d29212332) +- **manifest.json:** Auto-fill supportedLocales [`b085634`](https://github.com/SAP/ui5-builder/commit/b085634555193acc669ed37e8e4b817b9798698b) + +### BREAKING CHANGE + +Created bundles use sap.ui.predefine instead function wrappers. For +projects this is a compatible change. Only the produced bundle content +changes. + +Task API `namespace` option got renamed to `projectNamespace` + +JIRA: CPOUI5FOUNDATION-802 +Relates to: https://github.com/SAP/ui5-tooling/issues/701 + +Support for older Node.js and npm releases has been dropped. +Only Node.js 20.11.x and >=21.2.0 as well as npm v10 or higher are supported. + + +## [v3.5.1] - 2024-07-22 +### Bug Fixes +- **generateThemeDesignerResources:** Allow core .theming in sources ([#1062](https://github.com/SAP/ui5-builder/issues/1062)) [`dda3011`](https://github.com/SAP/ui5-builder/commit/dda30114461314e81da43bd739e709186744f592) + + +## [v3.5.0] - 2024-06-21 +### Features +- ES2023 Support [`097049d`](https://github.com/SAP/ui5-builder/commit/097049daeec5c54c2d5e858b12e0c54c826ff663) + + + +## [v3.4.1] - 2024-05-13 +### Bug Fixes +- **bundle/Builder:** Correct bundling of resources with empty source map [`1228db7`](https://github.com/SAP/ui5-builder/commit/1228db78e7e655cea58c20517662b08dd09db87b) + + + +## [v3.4.0] - 2024-04-24 +### Features +- **generateFlexChangesBundle:** Handle minUI5Version as array ([#1009](https://github.com/SAP/ui5-builder/issues/1009)) [`45c9b7e`](https://github.com/SAP/ui5-builder/commit/45c9b7efc255e1d62dfed82ccf67b61d54dd8627) + + + +## [v3.3.1] - 2024-03-27 + + +## [v3.3.0] - 2024-01-18 +### Features +- **flexChangesBundler:** Merge existing with new flexibility-bundle.json [`fd070ab`](https://github.com/SAP/ui5-builder/commit/fd070abaa22680ee70b448fa0ac406a8f2b57cc5) + + + +## [v3.2.0] - 2023-12-12 +### Bug Fixes +- Incomplete multi-character sanitization ([#959](https://github.com/SAP/ui5-builder/issues/959)) [`d61f1b7`](https://github.com/SAP/ui5-builder/commit/d61f1b744495f5428db33467218077e7996f1575) +- Add guard against prototype pollution ([#960](https://github.com/SAP/ui5-builder/issues/960)) [`ba230d9`](https://github.com/SAP/ui5-builder/commit/ba230d922cac0acd291dfe18b0ae7a95eae8b190) +- Incomplete string escaping or encoding ([#958](https://github.com/SAP/ui5-builder/issues/958)) [`50bb0d9`](https://github.com/SAP/ui5-builder/commit/50bb0d97e76fb312412cf29fae18b76cc88df6f4) +- **manifestCreator:** set fallbackLocale to empty string if no locale is present ([#962](https://github.com/SAP/ui5-builder/issues/962)) [`26526a0`](https://github.com/SAP/ui5-builder/commit/26526a08ff38ee11ed3bd506f7ef0610f1d1ccb0) + +### Features +- depCache bundling mode ([#951](https://github.com/SAP/ui5-builder/issues/951)) [`f2cf564`](https://github.com/SAP/ui5-builder/commit/f2cf564f0f71d635e58a743c7bdef1f427e341b2) + + + +## [v3.1.1] - 2023-11-20 +### Bug Fixes +- Handle graceful termination of workerpool for parallel builds ([#953](https://github.com/SAP/ui5-builder/issues/953)) [`f7b9f27`](https://github.com/SAP/ui5-builder/commit/f7b9f27ac5966bd89e52e4c2d5b03482a0f3dbb7) +- **Bundling:** Detect manifest.json dependency of libraries [`6f9995f`](https://github.com/SAP/ui5-builder/commit/6f9995f5b47a6094fa93b5d433be849b1d3cdc7e) + + + +## [v3.1.0] - 2023-10-11 +### Bug Fixes +- **bundle/Builder:** Add missing 'names' attribute to generated source maps [`57e0e50`](https://github.com/SAP/ui5-builder/commit/57e0e5047638a9a704a696b8af7780fbbceefbc4) + +### Features +- Validate apiVersion property in Library.init ([#943](https://github.com/SAP/ui5-builder/issues/943)) [`52bf258`](https://github.com/SAP/ui5-builder/commit/52bf25842a59c3fa1ddbe71b482b232e18b55288) +- **Minifier:** Support input source maps ([#780](https://github.com/SAP/ui5-builder/issues/780)) [`67ecb27`](https://github.com/SAP/ui5-builder/commit/67ecb27e44a2d84e6b2203f31049220dcbcd41f0) + +### Reverts +- [INTERNAL] Azure Pipelines: Switch back to Node 20.5.x + + + +## [v3.0.9] - 2023-07-26 + + +## [v3.0.8] - 2023-07-19 +### Bug Fixes +- Revert "[INTERNAL] Remove implicit dependencies concept ([#913](https://github.com/SAP/ui5-builder/issues/913))" [`1043714`](https://github.com/SAP/ui5-builder/commit/1043714e3b952a8280f1ff7909f79db3b750eb0c) +- **generateJsdoc:** Also process resources created by preceeding tasks [`04447bd`](https://github.com/SAP/ui5-builder/commit/04447bdec28b8bf18c24bd53e3fe8be9bdeed6c2) + + + +## [v3.0.7] - 2023-07-13 +### Bug Fixes +- Don't report a missing module that's contained in another bundle ([#915](https://github.com/SAP/ui5-builder/issues/915)) [`8f23f38`](https://github.com/SAP/ui5-builder/commit/8f23f388f6d64f313ae8a89d0fcaf39ba905a70b) + + + +## [v3.0.6] - 2023-06-21 + + +## [v3.0.5] - 2023-06-05 +### Bug Fixes +- **bundle/Builder:** Remove sourceMappingURL from modules embedded as string [`a2f410c`](https://github.com/SAP/ui5-builder/commit/a2f410c32945a6a25fdf47b7b06ccb7f21ef5716) + +### Dependency Updates +- Bump xml2js from 0.5.0 to 0.6.0 [`9679830`](https://github.com/SAP/ui5-builder/commit/967983000d64324089740c5ffc0460dcca23866f) + + + +## [v3.0.4] - 2023-05-23 +### Bug Fixes +- **lbt/Resolver:** ensure stable module order in generated bundleInfo [`482b8a0`](https://github.com/SAP/ui5-builder/commit/482b8a011d9f0e3fc24d0485be7caf1509195c82) + + + +## [v3.0.3] - 2023-04-21 +### Dependency Updates +- Bump rimraf from 4.4.1 to 5.0.0 ([#897](https://github.com/SAP/ui5-builder/issues/897)) [`7b5e442`](https://github.com/SAP/ui5-builder/commit/7b5e442be80ff2e070c249a2c3c3cbe11a34b956) + + + +## [v3.0.2] - 2023-04-12 +### Dependency Updates +- Bump xml2js from 0.4.23 to 0.5.0 [`5269fc2`](https://github.com/SAP/ui5-builder/commit/5269fc206b30a13d28493892cc658f1614e7cc74) + + + +## [v3.0.1] - 2023-02-16 + + +## [v3.0.0] - 2023-02-09 +### Breaking Changes +- Transform to ES Modules ([#790](https://github.com/SAP/ui5-builder/issues/790)) [`a439aa9`](https://github.com/SAP/ui5-builder/commit/a439aa9f679dfe2e18b8c540f54876f94c0de119) +- **generateJsDoc:** Remove internal 'buildContext' parameter [`4ec80c8`](https://github.com/SAP/ui5-builder/commit/4ec80c874e177b658b1cd819431b6cb0660ded87) +- Removal of manifestBundler and generateManifestBundle ([#838](https://github.com/SAP/ui5-builder/issues/838)) [`07a5be2`](https://github.com/SAP/ui5-builder/commit/07a5be2b6d9aa23cf78ddd17951c832d6dec7bef) +- **JSDoc:** Fail build when jsdoc command failed ([#845](https://github.com/SAP/ui5-builder/issues/845)) [`c2916b4`](https://github.com/SAP/ui5-builder/commit/c2916b4f1d49b5500e4b51143d4e6065ac200eef) +- Remove build execution ([#740](https://github.com/SAP/ui5-builder/issues/740)) [`af2e956`](https://github.com/SAP/ui5-builder/commit/af2e956f6cba1275669160328e32df3fffa782d0) +- Enable modern preload bundles without dependencies ([#739](https://github.com/SAP/ui5-builder/issues/739)) [`97cfa6c`](https://github.com/SAP/ui5-builder/commit/97cfa6cd3eadff57bcc18816534c7751551ebdb8) +- Require Node.js >= 16.18.0 / npm >= 8 [`dbf6c06`](https://github.com/SAP/ui5-builder/commit/dbf6c0694693070f73e9f96edef90f094d5bf721) +- **generateResourcesJson:** Make 'dependencies' parameter mandatory [`268dd16`](https://github.com/SAP/ui5-builder/commit/268dd16091c515ec0c922ea23af437d9aa8bf3ea) +- **moduleBundler:** Always default to `optimize: true` ([#685](https://github.com/SAP/ui5-builder/issues/685)) [`de5837c`](https://github.com/SAP/ui5-builder/commit/de5837c43449ea61deae3b2f02f9973f3fa37992) +- **libraryLessGenerator:** Throw error when import can't be inlined [`d2be9bb`](https://github.com/SAP/ui5-builder/commit/d2be9bb68600ec97b9bc007f348f87ad456fb5a3) + +### BREAKING CHANGE +This package has been transformed to ES Modules. Therefore it no longer provides a CommonJS export. +If your project uses CommonJS, it needs to be converted to ESM or use a dynamic import. + +For more information see also: + +- https://sap.github.io/ui5-tooling/updates/migrate-v3/ +- https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +The `jsdocGenerator` processor and the corresponding `generateJsdoc` task will now throw an error when JSDoc reports an error (exit code != 0). This will also fail the build when running `ui5 build jsdoc`. + +The manifestBundler processor and generateManifestBundle task has been removed because it is no longer required for the HTML5 repository in Cloud Foundry. + +- builder.js has been removed. Use ui5-project builder instead +- Tasks now rely on Project instances being available on Resources (see https://github.com/SAP/ui5-fs/pull/381) +- TaskRepository#addTask has been removed. Custom tasks need to be added to the project graph instead +- TaskUtil#get/set/clearTag now requires a Project instance to be provided. Path strings are no longer sufficient + and will cause an exception to be thrown + +For library projects, the task "generateComponentPreload" is now +executed after tasks "generateLibraryManifest" and +"generateManifestBundle" instead of before them. + +Support for older Node.js and npm releases has been dropped. +Only Node.js v16.18.0 and npm v8 or higher are supported. + +The following tasks have been removed: + +- createDebugFiles +- uglify + +The following processors have been removed: + +- debugFileCreator +- resourceCopier +- uglifier + +As a replacement, the new 'minify' task and 'minifier' processor can be +used. + +Note: The minify task is executed earlier, before the bundling +process takes place. Existing 'beforeTask' and 'afterTask' configuration of +custom tasks might need to be adapted to cater for this change. + +### Features +- Support ES2022 language features ([#848](https://github.com/SAP/ui5-builder/issues/848)) [`f9b8457`](https://github.com/SAP/ui5-builder/commit/f9b845726731a0e02ec4a499e2a1a82a639174a8) +- **jsdoc:** Support destructuring of enums for defaultValue ([#775](https://github.com/SAP/ui5-builder/issues/775)) [`523f365`](https://github.com/SAP/ui5-builder/commit/523f365cb917997c5031d245309c21e9e4b3e311) +- **builder:** Improve support for ES6+ syntax ([#774](https://github.com/SAP/ui5-builder/issues/774)) [`90385fe`](https://github.com/SAP/ui5-builder/commit/90385feb7cea1e5b864cff035b01263a492972a9) +- **jsdoc:** Improve support for ES6+ syntax ([#785](https://github.com/SAP/ui5-builder/issues/785)) [`187a6a3`](https://github.com/SAP/ui5-builder/commit/187a6a35000145d70bf41e0d8b724e5ea8d8dc78) +- **generateThemeDesignerResources task:** Create css_variables.less ([#730](https://github.com/SAP/ui5-builder/issues/730)) [`34e69be`](https://github.com/SAP/ui5-builder/commit/34e69be95fc8ec1961b24b7e2580c2c993d814d3) +- **builder:** Add cssVariables option ([#728](https://github.com/SAP/ui5-builder/issues/728)) [`30d58e1`](https://github.com/SAP/ui5-builder/commit/30d58e1081c1bdc665f13952ecbe5c400b5f4ed7) +- Generate source maps for bundles ([#695](https://github.com/SAP/ui5-builder/issues/695)) [`8a20c42`](https://github.com/SAP/ui5-builder/commit/8a20c4257a1ebe4d613b0595be93cd45d900f580) +- **replaceVersion/Copyright:** Also process test-resources ([#693](https://github.com/SAP/ui5-builder/issues/693)) [`a0d4bc2`](https://github.com/SAP/ui5-builder/commit/a0d4bc2a80d61e042786207af4bdbda6c3ddfe31) +- Support ES2022 language features [`e749b6a`](https://github.com/SAP/ui5-builder/commit/e749b6ae0838f923b27dd0d7d34da2174c433f5f) +- Add minify task and processor ([#666](https://github.com/SAP/ui5-builder/issues/666)) [`a3af604`](https://github.com/SAP/ui5-builder/commit/a3af604ff0a12fb9241dcd105cf0aec8d718a299) + +### Bug Fixes +- **package.json:** Downgrade es5-ext dependency [`2b71707`](https://github.com/SAP/ui5-builder/commit/2b71707683480b26aab9957c09e5d22bce8a6450) +- **minify:** Change default to not omit source map resources [`6d75a49`](https://github.com/SAP/ui5-builder/commit/6d75a495224590097332fc061c66cfe0e8f4c151) +- Avoid redundant bundle creation ([#741](https://github.com/SAP/ui5-builder/issues/741)) [`13c8405`](https://github.com/SAP/ui5-builder/commit/13c840585946401d238936dfa38a6f70a73e9ed0) +- **JSModuleAnalyzer:** Fix detection of bundle name ([#705](https://github.com/SAP/ui5-builder/issues/705)) [`aaeafd4`](https://github.com/SAP/ui5-builder/commit/aaeafd4a1fd194dd08e5ae47c29d90f0b4c7d197) +- **generateResourcesJson:** Add raw-module info for debug bundles ([#736](https://github.com/SAP/ui5-builder/issues/736)) [`3b918e8`](https://github.com/SAP/ui5-builder/commit/3b918e83bfd38342778ecd4c58e648e99ad7cffc) +- **LibraryBuilder:** Align task order of "generateComponentPreload" [`aea061d`](https://github.com/SAP/ui5-builder/commit/aea061d9d6c2ac0c11484dcc08bdcda23ab62986) +- **LocatorResourcePool:** Wait for resources in prepare step ([#719](https://github.com/SAP/ui5-builder/issues/719)) [`1b7f93f`](https://github.com/SAP/ui5-builder/commit/1b7f93f4988340d7a6575be3191a02e6c295ebd0) +- **XMLTemplateAnalyzer:** Analyze core:require of FragmentDefinition [`af075ed`](https://github.com/SAP/ui5-builder/commit/af075edf784d9f1ba162a34f0bf150dbcbc0f479) +- **Bundler:** Ensure reproducibility for bundles with multiple parts ([#689](https://github.com/SAP/ui5-builder/issues/689)) [`6f4588b`](https://github.com/SAP/ui5-builder/commit/6f4588b3eb687178e557ac4fb36481104f9681a0) +- **generateResourcesJson:** Don't list resources omitted from build result [`9608c51`](https://github.com/SAP/ui5-builder/commit/9608c5177f86494ba689d2e799b4378fec9ed6fa) +- **generateResourcesJson:** Analyze debug bundles ([#669](https://github.com/SAP/ui5-builder/issues/669)) [`f27513a`](https://github.com/SAP/ui5-builder/commit/f27513a259b30d39e260790781b6d435b4ab088b) +- **Bundler:** Sort raw modules by default [`0e11b69`](https://github.com/SAP/ui5-builder/commit/0e11b6965a1d2e63a96e9b738e38975b5ac755bc) + +### Dependency Updates +- Bump less-openui5 from 0.11.4 to 0.11.5 [`bf46354`](https://github.com/SAP/ui5-builder/commit/bf463541b7651db1dee167057eddc94181f5c1da) +- Bump less-openui5 from 0.11.3 to 0.11.4 [`b7a507f`](https://github.com/SAP/ui5-builder/commit/b7a507fbc34076f5bf95f92f058fd2c58d79a455) +- Bump less-openui5 from 0.11.2 to 0.11.3 [`28e684b`](https://github.com/SAP/ui5-builder/commit/28e684b85e7eb621f210041702e2a316d1d0eb62) +- Bump escope from 3.6.0 to 4.0.0 [`e25af0d`](https://github.com/SAP/ui5-builder/commit/e25af0d9c1184916aa037b4b4ca23d15c5f2a408) +- Bump rimraf from 3.0.2 to 4.1.1 ([#873](https://github.com/SAP/ui5-builder/issues/873)) [`ddb9660`](https://github.com/SAP/ui5-builder/commit/ddb96602fea5b96ba37afdebda0993d847462f52) +- Bump jsdoc from 3.6.11 to 4.0.0 ([#821](https://github.com/SAP/ui5-builder/issues/821)) [`6d05117`](https://github.com/SAP/ui5-builder/commit/6d0511785069d12a588b2ad9fa31de828700fef0) + + +## [v2.11.9] - 2022-11-30 +### Bug Fixes +- **transformApiJson:** Fix regression in UI5 Tooling scenario [`819c808`](https://github.com/SAP/ui5-builder/commit/819c808010963b84bfe33e109a7b2a5412096f78) + +### Dependency Updates +- Bump less-openui5 from 0.11.2 to 0.11.3 [`1bbe5f5`](https://github.com/SAP/ui5-builder/commit/1bbe5f561fb35c646a27cbe4d03050101ff68d7c) + + + +## [v2.11.8] - 2022-10-28 +### Bug Fixes +- **ComponentAnalyzer:** Fix detection of rootView [`acb3d9f`](https://github.com/SAP/ui5-builder/commit/acb3d9fd7fc60ad61ac88d68cb0fd5296b795e9f) +- **XMLTemplateAnalyzer:** Fix detection of nested views [`1cbfd82`](https://github.com/SAP/ui5-builder/commit/1cbfd82396e5fa78455e0a696e9229a2e8a337fc) + + + +## [v2.11.7] - 2022-10-20 +### Dependency Updates +- Bump escope from 3.6.0 to 4.0.0 [`852b37f`](https://github.com/SAP/ui5-builder/commit/852b37f549f3927decbc1b40cc6055325472d625) + +### Reverts +- [FIX] package.json: Downgrade es5-ext dependency ([#798](https://github.com/SAP/ui5-builder/issues/798)) + + + +## [v2.11.6] - 2022-10-12 +### Bug Fixes +- **package.json:** Downgrade es5-ext dependency ([#798](https://github.com/SAP/ui5-builder/issues/798)) [`0e18008`](https://github.com/SAP/ui5-builder/commit/0e18008d86da93c5c8743b17ac0091afc3064d78) + + + +## [v2.11.5] - 2022-05-04 + + + +## [v2.11.4] - 2022-02-21 +### Bug Fixes +- **generateFlexChangesBundle:** Fix minUI5Version check for UI5 v1.100.0+ ([#706](https://github.com/SAP/ui5-builder/issues/706)) [`fb1217a`](https://github.com/SAP/ui5-builder/commit/fb1217ac536c20da81526f82f2ecb03686815942) + + + +## [v2.11.3] - 2021-12-14 +### Bug Fixes +- Enable buildThemes for libraries without .library [`7b941a7`](https://github.com/SAP/ui5-builder/commit/7b941a797210463a9fa8ca50753662c5db373aa6) + + + +## [v2.11.2] - 2021-11-17 +### Bug Fixes +- **Builder:** Emit warning on bundleInfo name without extension [`ed0da12`](https://github.com/SAP/ui5-builder/commit/ed0da123ac084d126a28f0f34d9740c310d91902) +- **manifestCreator:** Trim whitespace/new-lines for sap.app/title [`019cfd7`](https://github.com/SAP/ui5-builder/commit/019cfd7031a2bd63ce93801ad027413151a3b060) + + + +## [v2.11.1] - 2021-10-19 +### Bug Fixes +- Minification excludes for application projects [`7f6fd68`](https://github.com/SAP/ui5-builder/commit/7f6fd68e1aed9131896723f1231816287eaf2fce) + + + +## [v2.11.0] - 2021-10-19 +### Bug Fixes +- **manifestCreator:** supportedThemes should only list relevant themes [`01f3859`](https://github.com/SAP/ui5-builder/commit/01f3859070b6955b9824b0949e633c6d40244633) + +### Features +- Support build minification excludes ([#653](https://github.com/SAP/ui5-builder/issues/653)) [`0aa2301`](https://github.com/SAP/ui5-builder/commit/0aa2301df4c5d40c531da52e2d6314955b95b396) + + + +## [v2.10.0] - 2021-10-05 +### Features +- Introduce build task replaceBuildtime [`2ad0960`](https://github.com/SAP/ui5-builder/commit/2ad09603deee3bc26eae36aa36a7a4ac10f83cb0) + + + +## [v2.9.5] - 2021-08-25 +### Bug Fixes +- **AbstractBuilder:** Multiple custom task executions did not run ([#636](https://github.com/SAP/ui5-builder/issues/636)) [`c05587a`](https://github.com/SAP/ui5-builder/commit/c05587a3cab93c8852832098318235dbfbcce49f) +- **Builder:** Handle files without extension ([#637](https://github.com/SAP/ui5-builder/issues/637)) [`07aaa24`](https://github.com/SAP/ui5-builder/commit/07aaa247ae8c07f8c7bb13a93439ef2c1270cb6c) + + + +## [v2.9.4] - 2021-07-23 +### Bug Fixes +- **XMLTemplateAnalyzer:** Include document name for XML parsing errors [`3c88ca4`](https://github.com/SAP/ui5-builder/commit/3c88ca47aae85892360e4e4cf4b50860a0f430e3) + + + +## [v2.9.3] - 2021-07-01 +### Bug Fixes +- **LibraryFormatter:** Fix handling of paths containing special characters [`2093562`](https://github.com/SAP/ui5-builder/commit/2093562adec588a9c1f50125e0b739836e6c76c8) + + + +## [v2.9.2] - 2021-06-17 +### Bug Fixes +- Switch from esprima to espree ([#615](https://github.com/SAP/ui5-builder/issues/615)) [`c3d50e3`](https://github.com/SAP/ui5-builder/commit/c3d50e36aacf374f7616278aa590a6423c3294fe) +- **lbt/resources/ResourceCollector:** Ensure proper matching of indicator files ([#614](https://github.com/SAP/ui5-builder/issues/614)) [`2d3a1d8`](https://github.com/SAP/ui5-builder/commit/2d3a1d86f06f0eb223b7ae9843d6479ebb1e14e4) + + + +## [v2.9.1] - 2021-06-08 +### Bug Fixes +- **JSDoc:** Use namespace to derive uilib name for jsdoc generator [`2077130`](https://github.com/SAP/ui5-builder/commit/2077130d52305e4f46c3230c94001812beb2eb36) +- **JSDoc:** add project name to JSDoc configuration and api.json ([#609](https://github.com/SAP/ui5-builder/issues/609)) [`419ce38`](https://github.com/SAP/ui5-builder/commit/419ce3836bce117fb3d5fc05af11caef5c8a432a) +- **lib/processors/debugFileCreator:** Add -dbg suffix only to files ([#611](https://github.com/SAP/ui5-builder/issues/611)) [`9da2f7b`](https://github.com/SAP/ui5-builder/commit/9da2f7b7b53b41adbce772e9823cee24b50a9aa9) + +### Dependency Updates +- Bump cheerio from 0.22.0 to 1.0.0-rc.9 [`8bdb146`](https://github.com/SAP/ui5-builder/commit/8bdb1462e721127eaf860665a1361b6877e873a1) + + + +## [v2.9.0] - 2021-06-01 +### Features +- Support writing 'bundles' config as part of a bundle ([#396](https://github.com/SAP/ui5-builder/issues/396)) [`b5f372a`](https://github.com/SAP/ui5-builder/commit/b5f372a6ab9c31f3acef41a59e6cecd6681be2dd) + + + +## [v2.8.4] - 2021-05-17 +### Bug Fixes +- **XMLTemplateAnalyzer:** Properly detect conditional dependencies [`0f56490`](https://github.com/SAP/ui5-builder/commit/0f56490cedcdebc363cc7f1d99f03cfde907804f) +- **lbt/bundle/Builder:** Preserve comments in bundles [`8dfa919`](https://github.com/SAP/ui5-builder/commit/8dfa9191d36cb60ce44b64536b6f6b2b766b87c2) + + + +## [v2.8.3] - 2021-04-19 +### Bug Fixes +- Emit warning when including/excluding unknown tasks [`854f456`](https://github.com/SAP/ui5-builder/commit/854f456e06e163e8423702e4fe893905e240adfa) + + + +## [v2.8.2] - 2021-03-11 +### Bug Fixes +- **LibraryFormatter:** Do not throw for missing .library in legacy OpenUI5 theme libraries [`f7e22ba`](https://github.com/SAP/ui5-builder/commit/f7e22ba866bf6a61b0d2932ef18aad53127641fd) + +### Dependency Updates +- Bump less-openui5 from 0.10.0 to 0.11.0 ([#594](https://github.com/SAP/ui5-builder/issues/594)) [`f3d174b`](https://github.com/SAP/ui5-builder/commit/f3d174be1fc2ab66a62632439592b2899680c093) + + + +## [v2.8.1] - 2021-03-04 +### Bug Fixes +- **generateResourcesJson:** Include dependencies of XML resources [`0fc364d`](https://github.com/SAP/ui5-builder/commit/0fc364ded64eb5bae4085397dc1831e04b19edf4) +- **manifestCreator:** 'embeds' should list all components ([#575](https://github.com/SAP/ui5-builder/issues/575)) [`11f823a`](https://github.com/SAP/ui5-builder/commit/11f823a77e72cfa4c096e7e8f4277a6a6b9400b8) +- **moduleBundler:** Apply defaultFileTypes ([#580](https://github.com/SAP/ui5-builder/issues/580)) [`42f6474`](https://github.com/SAP/ui5-builder/commit/42f64744a299e8548f6dbdb7bcbb8b3cef72f1f4) +- **resourceListCreator:** Include dependencies of subModules ([#588](https://github.com/SAP/ui5-builder/issues/588)) [`fe61d6e`](https://github.com/SAP/ui5-builder/commit/fe61d6eba6671ca31f7c49a7d1281adb6d5f2114) +- **versionInfoGenerator:** fix hasOwnPreload flag ([#591](https://github.com/SAP/ui5-builder/issues/591)) [`73a0f8b`](https://github.com/SAP/ui5-builder/commit/73a0f8baa0248aef3ac6c2b114082aa120ef6e22) + + + +## [v2.8.0] - 2021-02-09 +### Features +- Add Library/Component preload exclude configuration ([#573](https://github.com/SAP/ui5-builder/issues/573)) [`f1644a4`](https://github.com/SAP/ui5-builder/commit/f1644a4d75c4b3ffb5c092f3a24c74b9123afedc) + + + +## [v2.7.2] - 2021-01-29 +### Dependency Updates +- Bump less-openui5 from 0.9.0 to 0.10.0 [`4674fef`](https://github.com/SAP/ui5-builder/commit/4674fef348cf0f5b1ac76fff931335244d64e66f) + + + +## [v2.7.1] - 2021-01-28 +### Bug Fixes +- **JSDoc:** adapt sdkTransformer to change in transformApiJson.js ([#574](https://github.com/SAP/ui5-builder/issues/574)) [`655f731`](https://github.com/SAP/ui5-builder/commit/655f731191f1210d9f72bee9f60fcebdc863bc36) +- **versionInfoGenerator:** manifest without embeds [#486](https://github.com/SAP/ui5-builder/issues/486) ([#576](https://github.com/SAP/ui5-builder/issues/576)) [`4d86226`](https://github.com/SAP/ui5-builder/commit/4d86226abf6ce549f3cf719068270014ddeefb5a) + + + +## [v2.7.0] - 2021-01-26 +### Features +- Versioninfo enrich with manifest infos ([#554](https://github.com/SAP/ui5-builder/issues/554)) [`7603ece`](https://github.com/SAP/ui5-builder/commit/7603ece36a74592c7756f6147eed91d08a5788a6) +- Align JSDoc template & plugin with OpenUI5 1.87.0 ([#572](https://github.com/SAP/ui5-builder/issues/572)) [`0cb02ac`](https://github.com/SAP/ui5-builder/commit/0cb02acee2b070889146ef9f725cc139691f0ab2) + + + +## [v2.6.1] - 2021-01-21 +### Bug Fixes +- **Theme Build:** Only process themes within library namespace ([#570](https://github.com/SAP/ui5-builder/issues/570)) [`8cecc01`](https://github.com/SAP/ui5-builder/commit/8cecc01ccbd1a84e2ede91e618f31dcf6c00b3fd) +- **processors/libraryLessGenerator:** Don't throw in case of import errors [`0e25b59`](https://github.com/SAP/ui5-builder/commit/0e25b59fe8e7e37f4f6b5c947a76b4da6f79469f) + + + +## [v2.6.0] - 2021-01-14 +### Features +- Add 'generateThemeDesignerResources' task [`03241c0`](https://github.com/SAP/ui5-builder/commit/03241c0e2599cb0928cbbf34ddc678634b2d5a93) +- Add 'libraryLessGenerator' processor ([#560](https://github.com/SAP/ui5-builder/issues/560)) [`a7e1e5c`](https://github.com/SAP/ui5-builder/commit/a7e1e5c0c4b63d9bab3f6645deff8e0f4187d305) +- **manifestBundler:** Add support for sap.app/i18n/enhanceWith ([#564](https://github.com/SAP/ui5-builder/issues/564)) [`1b7a277`](https://github.com/SAP/ui5-builder/commit/1b7a277aeeba9a43b647a46ae4487878ca2d6219) +- **manifestCreator:** enrich manifest with supportedLocales in i18n (for libraries) ([#547](https://github.com/SAP/ui5-builder/issues/547)) [`8102034`](https://github.com/SAP/ui5-builder/commit/810203477647c52948eb357ce9679373d32dd9b1) + + + +## [v2.5.1] - 2020-12-18 +### Bug Fixes +- **Windows:** Correctly handle project paths containing non-ASCII characters [`b229bf3`](https://github.com/SAP/ui5-builder/commit/b229bf315097591d2e870b74fb2b92b26b178877) + + + +## [v2.5.0] - 2020-12-15 +### Bug Fixes +- **manifestCreator:** Add component path to error logs [`049b9ee`](https://github.com/SAP/ui5-builder/commit/049b9ee22f8bf6c1bb41f9ba32be65a8fce38f23) + +### Features +- **ApplicationFormatter:** Implement manifest.appdescr_variant fallback ([#545](https://github.com/SAP/ui5-builder/issues/545)) [`6d44481`](https://github.com/SAP/ui5-builder/commit/6d44481ad3668758d4c008d28b11cb47ca6bbee1) + + + +## [v2.4.5] - 2020-11-30 +### Bug Fixes +- **generateResourcesJson:** Make resources.json generation deterministic [`41d3335`](https://github.com/SAP/ui5-builder/commit/41d3335bbddaba2e65e3293b37f89010ab0cd6fc) +- **manifestCreator:** Only list components with corresponding 'embeddedBy' ([#555](https://github.com/SAP/ui5-builder/issues/555)) [`89872d7`](https://github.com/SAP/ui5-builder/commit/89872d79623accad1ed148034c1f2fe46e44eeee) + + + +## [v2.4.4] - 2020-11-25 +### Bug Fixes +- **JSModuleAnalyzer:** Properly handle jQuery.sap.registerPreloadedModules calls [`9433f6a`](https://github.com/SAP/ui5-builder/commit/9433f6a989d6fea46f637ac8ff58c739977f456c) + + + +## [v2.4.3] - 2020-11-06 + + +## [v2.4.2] - 2020-11-04 +### Reverts +- [FEATURE] Switch XML minifier from pretty-data to minify-xml + + + +## [v2.4.1] - 2020-11-03 +### Dependency Updates +- Bump minify-xml from 2.1.2 to 2.1.3 [`839d12b`](https://github.com/SAP/ui5-builder/commit/839d12b0b4150ef13c86e639576e5a29854dc7d9) + + + +## [v2.4.0] - 2020-11-03 +### Features +- Tag bundles and ignore them in uglify task ([#535](https://github.com/SAP/ui5-builder/issues/535)) [`b487366`](https://github.com/SAP/ui5-builder/commit/b4873663ea67fa16f8fd9c2672c647026894ba32) +- Switch XML minifier from pretty-data to minify-xml [`be29520`](https://github.com/SAP/ui5-builder/commit/be295203cf71740f0585ee59f44c55ee59e41b26) + + + +## [v2.3.0] - 2020-10-22 +### Features +- Create designtime and support bundles for libraries ([#529](https://github.com/SAP/ui5-builder/issues/529)) [`2a51943`](https://github.com/SAP/ui5-builder/commit/2a5194346279279a6fb28c7332245e1cc5360d63) + +### Performance Improvements +- **BundleWriter:** Improve performance ([#534](https://github.com/SAP/ui5-builder/issues/534)) [`750b43e`](https://github.com/SAP/ui5-builder/commit/750b43eb88aded89eb8cd0b4b9ccb1ca5d5f94d2) + + + +## [v2.2.1] - 2020-10-06 +### Bug Fixes +- **Bundler:** Improve error log messages ([#466](https://github.com/SAP/ui5-builder/issues/466)) [`6bb6235`](https://github.com/SAP/ui5-builder/commit/6bb6235464b54da4e13553ecf9e0fe0ebcb3fe61) +- **tasks/generateResourcesJson:** Handling for sap.ui.integration [`1191b3d`](https://github.com/SAP/ui5-builder/commit/1191b3d4fac9ab7b78467d254afa88041962c416) + +### Dependency Updates +- Bump terser from 4.8.0 to 5.2.1 ([#511](https://github.com/SAP/ui5-builder/issues/511)) [`18f0df8`](https://github.com/SAP/ui5-builder/commit/18f0df84d7f3f4c7de9b1cacf06a5f5d2f0de8a9) + + + +## [v2.2.0] - 2020-09-02 +### Bug Fixes +- SapUiDefine call should not fail when there's no factory function ([#491](https://github.com/SAP/ui5-builder/issues/491)) [`25c6a3c`](https://github.com/SAP/ui5-builder/commit/25c6a3c9cae0d41f2757a8f0641bc043e171201b) + +### Features +- Add generateResourcesJson task ([#390](https://github.com/SAP/ui5-builder/issues/390)) [`021f439`](https://github.com/SAP/ui5-builder/commit/021f439e4125403d0d9e2fa0b7bcd3174ceb46e6) + + + +## [v2.1.0] - 2020-08-11 +### Features +- Implement TaskUtil class [`a7074ae`](https://github.com/SAP/ui5-builder/commit/a7074aeb8167330fd1b6d30bf5b387a212cd6b1b) +- **generateFlexChangesBundle:** Hide bundle input from build result [`001183a`](https://github.com/SAP/ui5-builder/commit/001183a4981bb5fe43039cedfbea70c2090b24db) + + + +## [v2.0.7] - 2020-08-10 +### Bug Fixes +- **generateLibraryPreload:** Ignore missing modules ([#481](https://github.com/SAP/ui5-builder/issues/481)) [`97b339f`](https://github.com/SAP/ui5-builder/commit/97b339f9c5dbddc8b80ed11c68f557d4eddc7f0a) + +### Dependency Updates +- Pin estraverse to v5.1.0 [`e5bc455`](https://github.com/SAP/ui5-builder/commit/e5bc4552015b71678102fd922609ef184502410c) + + + +## [v2.0.6] - 2020-07-21 +### Bug Fixes +- **SmartTemplateAnalyzer:** Do not throw in case missing dependency is expected ([#479](https://github.com/SAP/ui5-builder/issues/479)) [`b2150c3`](https://github.com/SAP/ui5-builder/commit/b2150c303fb14cd07b1f1ecadd1db5117cc7dccf) + + + +## [v2.0.5] - 2020-07-14 +### Bug Fixes +- **Node.js API:** TypeScript type definition support ([#475](https://github.com/SAP/ui5-builder/issues/475)) [`7858810`](https://github.com/SAP/ui5-builder/commit/785881061fe72e25230573ffb6b2a440d6782792) +- **XMLTemplateAnalyzer:** Handle empty XML view/fragment ([#471](https://github.com/SAP/ui5-builder/issues/471)) [`7488d5f`](https://github.com/SAP/ui5-builder/commit/7488d5f2c9216ac87e47ac7019fbc18674e86e30) + + + +## [v2.0.4] - 2020-06-15 +### Bug Fixes +- **ComponentAnalyzer:** Properly handle sap.ui5/routing ([#463](https://github.com/SAP/ui5-builder/issues/463)) [`717f2ec`](https://github.com/SAP/ui5-builder/commit/717f2ec8e6b04e67966183d25cc0ae59db94f43b) + + + +## [v2.0.3] - 2020-05-19 +### Bug Fixes +- Align JSDoc template and scripts with OpenUI5 1.79 ([#460](https://github.com/SAP/ui5-builder/issues/460)) [`c868fa0`](https://github.com/SAP/ui5-builder/commit/c868fa0d0a4c46d6c3098785a23fee3b7097cf02) +- **manifestBundler:** Add support for i18n object configuration ([#458](https://github.com/SAP/ui5-builder/issues/458)) [`85c4e19`](https://github.com/SAP/ui5-builder/commit/85c4e1958adf407b0dc2f7d4b324e9de354ab670) + + + +## [v2.0.2] - 2020-05-14 + + +## [v2.0.1] - 2020-04-30 +### Bug Fixes +- Namespaces in API Reference (JSDoc) [`b2a9a10`](https://github.com/SAP/ui5-builder/commit/b2a9a10dfee0ab40ce47eb4fb28666f6ea1f2360) + + + +## [v2.0.0] - 2020-03-31 +### Breaking Changes +- Make namespace mandatory for application and library projects ([#430](https://github.com/SAP/ui5-builder/issues/430)) [`ee96c00`](https://github.com/SAP/ui5-builder/commit/ee96c00d762ce24bba39f6c947997fcbb79efaae) +- Require Node.js >= 10 [`5451765`](https://github.com/SAP/ui5-builder/commit/5451765f648ecfe2c057cc2feed2c8fb7e98ef00) +- **LibraryFormatter:** Ignore manifest.json of nested apps [`846e929`](https://github.com/SAP/ui5-builder/commit/846e9290ef29aadc1ad18203003983181cd9c23a) + +### Dependency Updates +- Bump globby from 10.0.2 to 11.0.0 ([#399](https://github.com/SAP/ui5-builder/issues/399)) [`29efbbd`](https://github.com/SAP/ui5-builder/commit/29efbbd8c5d8bf0aca19e75b08f7b3d6f89e8556) + +### Features +- **buildThemes:** Add filtering for available themes ([#419](https://github.com/SAP/ui5-builder/issues/419)) [`848c503`](https://github.com/SAP/ui5-builder/commit/848c5032e98d229a655ddd17f07e252b57838f29) + +### BREAKING CHANGE + +If a library contains both, a manifest.json and .library file, they must +either be located in the same directory or the manifest.json is ignored. +In cases where the manifest.json is located on a higher level or +different directory on the same level than a .library file, an exception +is thrown. + +UI5 Project must be able to determine the project's namespace, +otherwise an error is thrown. + +Support for older Node.js releases has been dropped. +Only Node.js v10 or higher is supported. + + + +## [v1.10.1] - 2020-02-24 +### Bug Fixes +- **ApplicationBuilder:** Fix pattern to glob for .library files [`032d9a9`](https://github.com/SAP/ui5-builder/commit/032d9a974373ffc504fc65b46befe523eb3e4c7d) + + + +## [v1.10.0] - 2020-02-10 +### Bug Fixes +- Ensure proper handling of multi-byte characters in streams ([#411](https://github.com/SAP/ui5-builder/issues/411)) [`e906ec0`](https://github.com/SAP/ui5-builder/commit/e906ec0c3c3eb9fef874f2b7666c692915a496c6) +- **Bundling:** Dynamic preload calls should not emit warnings [`4d22b37`](https://github.com/SAP/ui5-builder/commit/4d22b37852ec130fb3198476e4a6225a47e2b657) + +### Features +- Add experimental CSS variables and skeleton build ([#393](https://github.com/SAP/ui5-builder/issues/393)) [`df8c39b`](https://github.com/SAP/ui5-builder/commit/df8c39b3f5a69086662b6f92c32e1364c1a93903) + + + +## [v1.9.0] - 2020-01-13 +### Bug Fixes +- Use 'defaultFileTypes' from bundle configuration ([#385](https://github.com/SAP/ui5-builder/issues/385)) [`c21e13e`](https://github.com/SAP/ui5-builder/commit/c21e13ea2d7f629b1f91b9acf625989f396c6b4f) +- Detect dynamic dependencies also when newer APIs are used ([#391](https://github.com/SAP/ui5-builder/issues/391)) [`ed1cc9d`](https://github.com/SAP/ui5-builder/commit/ed1cc9d45e517b3b38815483cc60fa7182ffd067) + +### Features +- Add new theme-library type ([#285](https://github.com/SAP/ui5-builder/issues/285)) [`a59287b`](https://github.com/SAP/ui5-builder/commit/a59287b670e956ef29ffe10bbbe1c3506ea3b330) +- **AbstractBuilder:** Allow adding custom tasks for types that have no standard tasks [`654450d`](https://github.com/SAP/ui5-builder/commit/654450df07c22bd1930c014f8b3d6904df8248e9) + + + +## [v1.8.0] - 2019-12-16 +### Features +- Add included/excludedDependencies parameter ([#380](https://github.com/SAP/ui5-builder/issues/380)) [`d6ac24a`](https://github.com/SAP/ui5-builder/commit/d6ac24ab76445568afab3fce9c813a0d5c4c4331) + + + +## [v1.7.1] - 2019-11-18 +### Dependency Updates +- Bump less-openui5 from 0.7.0 to 0.8.0 [`11101d4`](https://github.com/SAP/ui5-builder/commit/11101d4090718f6bee9f6b4851e05b1e1f33d57b) + + + +## [v1.7.0] - 2019-11-07 +### Bug Fixes +- **JSDoc:** Use the rel="noopener" attribute for external links. ([#361](https://github.com/SAP/ui5-builder/issues/361)) [`c702104`](https://github.com/SAP/ui5-builder/commit/c7021046af2ac66aaef8db3841192da8a254d304) + +### Dependency Updates +- Bump less-openui5 from 0.6.0 to 0.7.0 [`fdb0241`](https://github.com/SAP/ui5-builder/commit/fdb0241faec60062b1da52cc296dc343507fb802) + +### Features +- **buildThemes:** Add "compress" option ([#363](https://github.com/SAP/ui5-builder/issues/363)) [`3a0cf6a`](https://github.com/SAP/ui5-builder/commit/3a0cf6aa990a48830d3c22dac285036a290534d8) +- **flexChangesBundler:** Add flexibility-bundle.json ([#353](https://github.com/SAP/ui5-builder/issues/353)) [`cecc97d`](https://github.com/SAP/ui5-builder/commit/cecc97dd626268da2d2c707c5e0a6fabbfc561b6) + + + +## [v1.6.1] - 2019-10-24 +### Bug Fixes +- **jsdoc:** Adopt version range to micro releases ([#357](https://github.com/SAP/ui5-builder/issues/357)) [`619b959`](https://github.com/SAP/ui5-builder/commit/619b959d93441fef1be8c1609ebe5a9eb15759f5) + + + +## [v1.6.0] - 2019-10-24 +### Bug Fixes +- Update JSDoc to 3.6.3 ([#346](https://github.com/SAP/ui5-builder/issues/346)) [`78e2a22`](https://github.com/SAP/ui5-builder/commit/78e2a229f2ae11ca37538a75ac6746ff92af7b84) + +### Features +- **Simple Build Extensibility:** Pass project namespace to custom tasks [`1a167c5`](https://github.com/SAP/ui5-builder/commit/1a167c560ed8cc4e2c28a6c11efb1bf5ed142be9) + + + +## [v1.5.3] - 2019-10-11 +### Bug Fixes +- **Bundling:** merge dependency analysis results with raw module infos ([#340](https://github.com/SAP/ui5-builder/issues/340)) [`af4318a`](https://github.com/SAP/ui5-builder/commit/af4318a75d742bbd2e5566d2ffde2bc5a823ef06) + + + +## [v1.5.2] - 2019-10-09 +### Bug Fixes +- Improve recognition of main module in case of bundles ([#341](https://github.com/SAP/ui5-builder/issues/341)) [`7a560b4`](https://github.com/SAP/ui5-builder/commit/7a560b4bbc4c862ebded6f9e9f12c2156b1e33d1) +- Align set of known file types with runtime ([#337](https://github.com/SAP/ui5-builder/issues/337)) [`8b372f1`](https://github.com/SAP/ui5-builder/commit/8b372f1ad65d0edfe5cd440bd9352db7e48ea156) +- **manifestCreator:** Only consider component files called Component.js ([#273](https://github.com/SAP/ui5-builder/issues/273)) [`82fe267`](https://github.com/SAP/ui5-builder/commit/82fe2675114c13603238889e43be498f92d22a51) + + + +## [v1.5.1] - 2019-09-04 +### Bug Fixes +- **XMLTemplateAnalyzer:** Throws on tags without attributes ([#322](https://github.com/SAP/ui5-builder/issues/322)) [`b7f3795`](https://github.com/SAP/ui5-builder/commit/b7f379580d92e2d105edfc14e8feceab853f9a11) + + + +## [v1.5.0] - 2019-09-02 +### Features +- **XMLTemplateAnalyzer:** Support core:require ([#304](https://github.com/SAP/ui5-builder/issues/304)) [`b01fd85`](https://github.com/SAP/ui5-builder/commit/b01fd8538fafd33a4fc6303c58afe039d5ca1341) + + + +## [v1.4.2] - 2019-08-28 +### Bug Fixes +- Add 'sap.ui.fl' dependency to manifest.json ([#318](https://github.com/SAP/ui5-builder/issues/318)) [`a8edff4`](https://github.com/SAP/ui5-builder/commit/a8edff4cf63547cc1fc1d1c0ddfe958104fcb801) + + + +## [v1.4.1] - 2019-08-14 +### Bug Fixes +- Adapt to recent extension of estraverse's set of node types ([#310](https://github.com/SAP/ui5-builder/issues/310)) [`9db14e6`](https://github.com/SAP/ui5-builder/commit/9db14e6afc01c686c1187d8eefe327654e6cc3ca) + +### Features +- Switch to Terser for JavaScript minification [`fccb514`](https://github.com/SAP/ui5-builder/commit/fccb5145d05a8509d5b9c47fa4cea4b6299ca91d) +- **Theme Build:** Add compress option to minify output ([#295](https://github.com/SAP/ui5-builder/issues/295)) [`eea10ba`](https://github.com/SAP/ui5-builder/commit/eea10ba516c36be6aa3cdb2c8be990bc56f14078) +- **clean build folder:** Allows developers to clean build folder before start building a project [`04eb695`](https://github.com/SAP/ui5-builder/commit/04eb695fd493ce9bd1289933d5494178c1e679d7) + + + +## [v1.4.0] - 2019-07-29 +### Bug Fixes +- **versionInfo:** Use correct buildTimestamp format [`6d87b3e`](https://github.com/SAP/ui5-builder/commit/6d87b3e10db11a8755b4049ba82732c6ec4f776c) + +### Features +- Properties File Escaping ([#293](https://github.com/SAP/ui5-builder/issues/293)) [`9d213ce`](https://github.com/SAP/ui5-builder/commit/9d213ced942ed7832fbb7b50f9d444f441941f35) + + + +## [v1.3.3] - 2019-07-01 +### Bug Fixes +- Use consistent RegExp to detect copyright comments ([#275](https://github.com/SAP/ui5-builder/issues/275)) [`bd7aa40`](https://github.com/SAP/ui5-builder/commit/bd7aa409be340216a88ceb2607e85d951c9de58a) + +### Dependency Updates +- Bump globby from 9.2.0 to 10.0.0 [`1ea4a11`](https://github.com/SAP/ui5-builder/commit/1ea4a11e7177602b11049bb42e7c4149a0d55ff2) + + + +## [v1.3.2] - 2019-06-24 +### Bug Fixes +- **generateManifestBundle:** Only glob files from project namespace [`fc7f659`](https://github.com/SAP/ui5-builder/commit/fc7f659ab45a6828a1ab05a35dbe856a4a2b5f87) + + + +## [v1.3.1] - 2019-06-14 +### Bug Fixes +- Detect library namespace automatically ([#255](https://github.com/SAP/ui5-builder/issues/255)) [`604d4d3`](https://github.com/SAP/ui5-builder/commit/604d4d36745c9581969c411a0a78e56981948d0e) + + + +## [v1.3.0] - 2019-06-03 +### Features +- **Builder:** Add excludes option ([#254](https://github.com/SAP/ui5-builder/issues/254)) [`6a7883e`](https://github.com/SAP/ui5-builder/commit/6a7883e9c39220084660993f77c0d4c4c37ec29c) + + + +## [v1.2.3] - 2019-05-15 +### Bug Fixes +- **JSDoc:** Implement own tmp dir lifecycle [`3f85abf`](https://github.com/SAP/ui5-builder/commit/3f85abfe9bf05e008c43cf6489d26ecb0b7d8ee3) + + + +## [v1.2.2] - 2019-05-08 +### Bug Fixes +- **package.json:** Fix JSDoc version to 3.5.5 [`873469d`](https://github.com/SAP/ui5-builder/commit/873469d0d9295a7d7d5775f446c170068d086502) + + + +## [v1.2.1] - 2019-05-07 +### Bug Fixes +- **SmartTemplateAnalyzer:** Detect dependencies from "pages" object [`2d400c2`](https://github.com/SAP/ui5-builder/commit/2d400c2ac0883ad57b4aa894c46a0dd5aecb070a) + + + +## [v1.2.0] - 2019-04-25 +### Features +- Add option to use hash signatures in cachebuster info file [`a4e8338`](https://github.com/SAP/ui5-builder/commit/a4e83383c7371cdde8573a901fdadd2ab243440e) + + + +## [v1.1.1] - 2019-04-24 +### Bug Fixes +- **ApplicationFormatter:** detect the namespace for Maven placeholders ([#243](https://github.com/SAP/ui5-builder/issues/243)) [`49ecb07`](https://github.com/SAP/ui5-builder/commit/49ecb07f41efdf0778f04b05117e0daae01e8710) + + + +## [v1.1.0] - 2019-04-12 +### Features +- Build the manifest-bundle.zip for applications and libraries [`f53aeea`](https://github.com/SAP/ui5-builder/commit/f53aeea594071616974d0e14b6d41609603bbd5b) +- Generate the AppCacheBuster index file for apps [`dd653c8`](https://github.com/SAP/ui5-builder/commit/dd653c8f3883da41f5723093d7e40aeb3258c180) + + + +## [v1.0.5] - 2019-04-03 +### Bug Fixes +- Generate sap-ui-custom-dbg.js for self-contained build ([#234](https://github.com/SAP/ui5-builder/issues/234)) [`d769d98`](https://github.com/SAP/ui5-builder/commit/d769d9894fe0a9d5262aea2cde86b463bc55433d) +- Add bundling sap-ui-core-noJQuery.js and sap-ui-core-noJQuery-dbg.js ([#235](https://github.com/SAP/ui5-builder/issues/235)) [`e7a7a63`](https://github.com/SAP/ui5-builder/commit/e7a7a63983dec54f53ac1c906eb2f970948db25d) + + + +## [v1.0.4] - 2019-03-27 +### Dependency Updates +- Bump tmp from 0.0.33 to 0.1.0 ([#220](https://github.com/SAP/ui5-builder/issues/220)) [`4fa642c`](https://github.com/SAP/ui5-builder/commit/4fa642c460f71b48ff690e3dc09de8cb0decca4e) + + + +## [v1.0.3] - 2019-03-21 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 1.0.1 to 1.0.2 ([#214](https://github.com/SAP/ui5-builder/issues/214)) [`eb85e0a`](https://github.com/SAP/ui5-builder/commit/eb85e0afa1e5e82571312448ce8ab7ef87a7bcbc) +- Bump [@ui5](https://github.com/ui5)/logger from 1.0.0 to 1.0.1 ([#212](https://github.com/SAP/ui5-builder/issues/212)) [`20557e8`](https://github.com/SAP/ui5-builder/commit/20557e85ac0de835b5d5ff455d613d102521d3c7) + +### Features +- Add JSDoc build functionalities ([#42](https://github.com/SAP/ui5-builder/issues/42)) [`293a4b0`](https://github.com/SAP/ui5-builder/commit/293a4b0ae44706490fb568be69d4032150a2891a) + + + +## [v1.0.2] - 2019-02-28 +### Bug Fixes +- Warning log of duplicate module declaration [`9a790a3`](https://github.com/SAP/ui5-builder/commit/9a790a30905cdebe6ba3db283b75983135b967d6) +- **Bundler:** Create sap-ui-core-dbg.js ([#176](https://github.com/SAP/ui5-builder/issues/176)) [`feb95e4`](https://github.com/SAP/ui5-builder/commit/feb95e41c199d5b455272ba5886cdd79d1502cd7) +- **ComponentAnalyzer:** Detect model types from dataSource [`efc5cef`](https://github.com/SAP/ui5-builder/commit/efc5cef5fb2988e78dfd1ea26f3c6ba818c69d87) + + + +## [v1.0.1] - 2019-02-01 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 1.0.0 to 1.0.1 [`55ab125`](https://github.com/SAP/ui5-builder/commit/55ab125e60c138a5a419cae1064590e5e535d893) + + + +## [v1.0.0] - 2019-02-01 +### Dependency Updates +- Bump [@ui5](https://github.com/ui5)/fs from 0.2.0 to 1.0.0 ([#142](https://github.com/SAP/ui5-builder/issues/142)) [`2c6893f`](https://github.com/SAP/ui5-builder/commit/2c6893f9029c161e95a4078caeb7e9f3a22a3af2) + +### Features +- Add transformation of apps index.html in self-contained build ([#137](https://github.com/SAP/ui5-builder/issues/137)) [`6549b8a`](https://github.com/SAP/ui5-builder/commit/6549b8a832cc50749159f1295bd93ef6a04733b6) + +### BREAKING CHANGE + +When running a self-contained build on an application project, the +index.html will be transformed by adopting the UI5 bootstrap script tag +to load the custom bundle file instead. + + + +## [v0.2.9] - 2019-01-03 +### Bug Fixes +- **ComponentAnalyzer:** Fully handle sap.ui5/routing ([#124](https://github.com/SAP/ui5-builder/issues/124)) [`c59b5b1`](https://github.com/SAP/ui5-builder/commit/c59b5b1efdc3a588fb8a13029a6593feab142e0c) +- **XMLTemplateAnalyzer:** Ignore properties with data binding [`0d5cf50`](https://github.com/SAP/ui5-builder/commit/0d5cf5086566dd0609fa354a5822f6538b335065) + + + +## [v0.2.8] - 2018-12-19 +### Bug Fixes +- Themes not beeing build [`de26564`](https://github.com/SAP/ui5-builder/commit/de26564c2c3af3376ccf179c972eae4f0e5eeeee) + + + +## [v0.2.7] - 2018-12-19 +### Bug Fixes +- **ComponentAnalyzer:** Handle sap.ui5/rootView with type string [`469e558`](https://github.com/SAP/ui5-builder/commit/469e558cae43d6a0c063170dd23e2337c0e5af26) +- **generateLibraryPreload:** Fix sap-ui-core.js bootstrap [`7a266fd`](https://github.com/SAP/ui5-builder/commit/7a266fd48d6452ce7f6180b026109d47caf195ec) + + + +## [v0.2.6] - 2018-12-06 + + +## [v0.2.5] - 2018-11-16 +### Features +- **Builder:** Add handling for custom task configurations [`9b4ae00`](https://github.com/SAP/ui5-builder/commit/9b4ae00f62da1f5bb94aeb8a86711c2a2e98da20) + + + +## [v0.2.4] - 2018-10-29 +### Features +- Add module type [`d7efb8a`](https://github.com/SAP/ui5-builder/commit/d7efb8a16334571e7997daccd4f69e1e06591c25) + + + +## [v0.2.3] - 2018-10-09 +### Bug Fixes +- Replace copyright and version strings in *.json and .library files [`f305429`](https://github.com/SAP/ui5-builder/commit/f305429067610404f0958b55ef3a570e555a532e) +- **generateLibraryPreload:** Add new sap.ui.core library namespaces [`ea901a7`](https://github.com/SAP/ui5-builder/commit/ea901a78c27e5fd112f9ac761e621b7f1c474f07) + + + +## [v0.2.2] - 2018-10-05 +### Bug Fixes +- **processors/versionInfoGenerator:** Remove "gav" property [`2bf41e1`](https://github.com/SAP/ui5-builder/commit/2bf41e1622df70818f925aabafe16de082fa3884) + +### Features +- **BundleBuilder:** support modules using ES6 with usePredefineCalls ([#67](https://github.com/SAP/ui5-builder/issues/67)) [`d1a4f1f`](https://github.com/SAP/ui5-builder/commit/d1a4f1f39e4262eafa8df1548f0e944998fd00a3) + + + +## [v0.2.1] - 2018-07-17 +### Bug Fixes +- **generateLibraryManifest:** i18n/css handling [`4e52a96`](https://github.com/SAP/ui5-builder/commit/4e52a9654b28a7646597ce0e0f010589ff7905d5) + + + +## [v0.2.0] - 2018-07-11 +### Bug Fixes +- Close gaps in routing support of ComponentAnalyzer ([#46](https://github.com/SAP/ui5-builder/issues/46)) [`4697531`](https://github.com/SAP/ui5-builder/commit/4697531cbafebf881e78b80e78d098d1361fe9a5) + + + +## [v0.1.1] - 2018-07-02 +### Bug Fixes +- iterate over routes using a for loop if it is an object ([#31](https://github.com/SAP/ui5-builder/issues/31)) [`e9823f6`](https://github.com/SAP/ui5-builder/commit/e9823f68cf038b5fde172916e483a01d5eb88f1f) + + + +## [v0.1.0] - 2018-06-26 +### Bug Fixes +- Bundles should be built one after another [`164ba32`](https://github.com/SAP/ui5-builder/commit/164ba328c6e172297d71b9d3ef871005931cca71) +- reduce build time at the price of a slightly increased code size ([#37](https://github.com/SAP/ui5-builder/issues/37)) [`1fb8d00`](https://github.com/SAP/ui5-builder/commit/1fb8d0049235467fcbd40f53e725cc419a8bc730) +- Use the target bundle format to decide decoration ([#24](https://github.com/SAP/ui5-builder/issues/24)) [`83703bc`](https://github.com/SAP/ui5-builder/commit/83703bca17fd18b9ac700fae4801d87a4d86961d) + + + +## [v0.0.2] - 2018-06-21 +### Bug Fixes +- **Builders:** Do not bundle debug files [`19800a1`](https://github.com/SAP/ui5-builder/commit/19800a16689210c13495bc1bd0949896500cfc52) + + + +## v0.0.1 - 2018-06-06 +### Bug Fixes +- Restore default component preload [`a09bec2`](https://github.com/SAP/ui5-builder/commit/a09bec2f57f45a1c5d74681b3bdec4f7fdc45343) + +### Features +- Add ability to configure component preloads and custom bundles [`2241e5f`](https://github.com/SAP/ui5-builder/commit/2241e5ff98fd95f1f80cc74959655ae7a9c660e7) +[v4.0.11]: https://github.com/SAP/ui5-builder/compare/v4.0.10...v4.0.11 +[v4.0.10]: https://github.com/SAP/ui5-builder/compare/v4.0.9...v4.0.10 +[v4.0.9]: https://github.com/SAP/ui5-builder/compare/v4.0.8...v4.0.9 +[v4.0.8]: https://github.com/SAP/ui5-builder/compare/v4.0.7...v4.0.8 +[v4.0.7]: https://github.com/SAP/ui5-builder/compare/v4.0.6...v4.0.7 +[v4.0.6]: https://github.com/SAP/ui5-builder/compare/v4.0.5...v4.0.6 +[v4.0.5]: https://github.com/SAP/ui5-builder/compare/v4.0.4...v4.0.5 +[v4.0.4]: https://github.com/SAP/ui5-builder/compare/v4.0.3...v4.0.4 +[v4.0.3]: https://github.com/SAP/ui5-builder/compare/v4.0.2...v4.0.3 +[v4.0.2]: https://github.com/SAP/ui5-builder/compare/v4.0.1...v4.0.2 +[v4.0.1]: https://github.com/SAP/ui5-builder/compare/v4.0.0...v4.0.1 +[v4.0.0]: https://github.com/SAP/ui5-builder/compare/v3.3.0...v4.0.0 +[v3.5.1]: https://github.com/SAP/ui5-builder/compare/v3.5.0...v3.5.1 +[v3.5.0]: https://github.com/SAP/ui5-builder/compare/v3.4.1...v3.5.0 +[v3.4.1]: https://github.com/SAP/ui5-builder/compare/v3.4.0...v3.4.1 +[v3.4.0]: https://github.com/SAP/ui5-builder/compare/v3.3.1...v3.4.0 +[v3.3.1]: https://github.com/SAP/ui5-builder/compare/v3.3.0...v3.3.1 +[v3.3.0]: https://github.com/SAP/ui5-builder/compare/v3.2.0...v3.3.0 +[v3.2.0]: https://github.com/SAP/ui5-builder/compare/v3.1.1...v3.2.0 +[v3.1.1]: https://github.com/SAP/ui5-builder/compare/v3.1.0...v3.1.1 +[v3.1.0]: https://github.com/SAP/ui5-builder/compare/v3.0.9...v3.1.0 +[v3.0.9]: https://github.com/SAP/ui5-builder/compare/v3.0.8...v3.0.9 +[v3.0.8]: https://github.com/SAP/ui5-builder/compare/v3.0.7...v3.0.8 +[v3.0.7]: https://github.com/SAP/ui5-builder/compare/v3.0.6...v3.0.7 +[v3.0.6]: https://github.com/SAP/ui5-builder/compare/v3.0.5...v3.0.6 +[v3.0.5]: https://github.com/SAP/ui5-builder/compare/v3.0.4...v3.0.5 +[v3.0.4]: https://github.com/SAP/ui5-builder/compare/v3.0.3...v3.0.4 +[v3.0.3]: https://github.com/SAP/ui5-builder/compare/v3.0.2...v3.0.3 +[v3.0.2]: https://github.com/SAP/ui5-builder/compare/v3.0.1...v3.0.2 +[v3.0.1]: https://github.com/SAP/ui5-builder/compare/v3.0.0...v3.0.1 +[v3.0.0]: https://github.com/SAP/ui5-builder/compare/v2.11.2...v3.0.0 +[v2.11.9]: https://github.com/SAP/ui5-builder/compare/v2.11.8...v2.11.9 +[v2.11.8]: https://github.com/SAP/ui5-builder/compare/v2.11.7...v2.11.8 +[v2.11.7]: https://github.com/SAP/ui5-builder/compare/v2.11.6...v2.11.7 +[v2.11.6]: https://github.com/SAP/ui5-builder/compare/v2.11.5...v2.11.6 +[v2.11.5]: https://github.com/SAP/ui5-builder/compare/v2.11.4...v2.11.5 +[v2.11.4]: https://github.com/SAP/ui5-builder/compare/v2.11.3...v2.11.4 +[v2.11.3]: https://github.com/SAP/ui5-builder/compare/v2.11.2...v2.11.3 +[v2.11.2]: https://github.com/SAP/ui5-builder/compare/v2.11.1...v2.11.2 +[v2.11.1]: https://github.com/SAP/ui5-builder/compare/v2.11.0...v2.11.1 +[v2.11.0]: https://github.com/SAP/ui5-builder/compare/v2.10.0...v2.11.0 +[v2.10.0]: https://github.com/SAP/ui5-builder/compare/v2.9.5...v2.10.0 +[v2.9.5]: https://github.com/SAP/ui5-builder/compare/v2.9.4...v2.9.5 +[v2.9.4]: https://github.com/SAP/ui5-builder/compare/v2.9.3...v2.9.4 +[v2.9.3]: https://github.com/SAP/ui5-builder/compare/v2.9.2...v2.9.3 +[v2.9.2]: https://github.com/SAP/ui5-builder/compare/v2.9.1...v2.9.2 +[v2.9.1]: https://github.com/SAP/ui5-builder/compare/v2.9.0...v2.9.1 +[v2.9.0]: https://github.com/SAP/ui5-builder/compare/v2.8.4...v2.9.0 +[v2.8.4]: https://github.com/SAP/ui5-builder/compare/v2.8.3...v2.8.4 +[v2.8.3]: https://github.com/SAP/ui5-builder/compare/v2.8.2...v2.8.3 +[v2.8.2]: https://github.com/SAP/ui5-builder/compare/v2.8.1...v2.8.2 +[v2.8.1]: https://github.com/SAP/ui5-builder/compare/v2.8.0...v2.8.1 +[v2.8.0]: https://github.com/SAP/ui5-builder/compare/v2.7.2...v2.8.0 +[v2.7.2]: https://github.com/SAP/ui5-builder/compare/v2.7.1...v2.7.2 +[v2.7.1]: https://github.com/SAP/ui5-builder/compare/v2.7.0...v2.7.1 +[v2.7.0]: https://github.com/SAP/ui5-builder/compare/v2.6.1...v2.7.0 +[v2.6.1]: https://github.com/SAP/ui5-builder/compare/v2.6.0...v2.6.1 +[v2.6.0]: https://github.com/SAP/ui5-builder/compare/v2.5.1...v2.6.0 +[v2.5.1]: https://github.com/SAP/ui5-builder/compare/v2.5.0...v2.5.1 +[v2.5.0]: https://github.com/SAP/ui5-builder/compare/v2.4.5...v2.5.0 +[v2.4.5]: https://github.com/SAP/ui5-builder/compare/v2.4.4...v2.4.5 +[v2.4.4]: https://github.com/SAP/ui5-builder/compare/v2.4.3...v2.4.4 +[v2.4.3]: https://github.com/SAP/ui5-builder/compare/v2.4.2...v2.4.3 +[v2.4.2]: https://github.com/SAP/ui5-builder/compare/v2.4.1...v2.4.2 +[v2.4.1]: https://github.com/SAP/ui5-builder/compare/v2.4.0...v2.4.1 +[v2.4.0]: https://github.com/SAP/ui5-builder/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/SAP/ui5-builder/compare/v2.2.1...v2.3.0 +[v2.2.1]: https://github.com/SAP/ui5-builder/compare/v2.2.0...v2.2.1 +[v2.2.0]: https://github.com/SAP/ui5-builder/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/SAP/ui5-builder/compare/v2.0.7...v2.1.0 +[v2.0.7]: https://github.com/SAP/ui5-builder/compare/v2.0.6...v2.0.7 +[v2.0.6]: https://github.com/SAP/ui5-builder/compare/v2.0.5...v2.0.6 +[v2.0.5]: https://github.com/SAP/ui5-builder/compare/v2.0.4...v2.0.5 +[v2.0.4]: https://github.com/SAP/ui5-builder/compare/v2.0.3...v2.0.4 +[v2.0.3]: https://github.com/SAP/ui5-builder/compare/v2.0.2...v2.0.3 +[v2.0.2]: https://github.com/SAP/ui5-builder/compare/v2.0.1...v2.0.2 +[v2.0.1]: https://github.com/SAP/ui5-builder/compare/v2.0.0...v2.0.1 +[v2.0.0]: https://github.com/SAP/ui5-builder/compare/v1.10.1...v2.0.0 +[v1.10.1]: https://github.com/SAP/ui5-builder/compare/v1.10.0...v1.10.1 +[v1.10.0]: https://github.com/SAP/ui5-builder/compare/v1.9.0...v1.10.0 +[v1.9.0]: https://github.com/SAP/ui5-builder/compare/v1.8.0...v1.9.0 +[v1.8.0]: https://github.com/SAP/ui5-builder/compare/v1.7.1...v1.8.0 +[v1.7.1]: https://github.com/SAP/ui5-builder/compare/v1.7.0...v1.7.1 +[v1.7.0]: https://github.com/SAP/ui5-builder/compare/v1.6.1...v1.7.0 +[v1.6.1]: https://github.com/SAP/ui5-builder/compare/v1.6.0...v1.6.1 +[v1.6.0]: https://github.com/SAP/ui5-builder/compare/v1.5.3...v1.6.0 +[v1.5.3]: https://github.com/SAP/ui5-builder/compare/v1.5.2...v1.5.3 +[v1.5.2]: https://github.com/SAP/ui5-builder/compare/v1.5.1...v1.5.2 +[v1.5.1]: https://github.com/SAP/ui5-builder/compare/v1.5.0...v1.5.1 +[v1.5.0]: https://github.com/SAP/ui5-builder/compare/v1.4.2...v1.5.0 +[v1.4.2]: https://github.com/SAP/ui5-builder/compare/v1.4.1...v1.4.2 +[v1.4.1]: https://github.com/SAP/ui5-builder/compare/v1.4.0...v1.4.1 +[v1.4.0]: https://github.com/SAP/ui5-builder/compare/v1.3.3...v1.4.0 +[v1.3.3]: https://github.com/SAP/ui5-builder/compare/v1.3.2...v1.3.3 +[v1.3.2]: https://github.com/SAP/ui5-builder/compare/v1.3.1...v1.3.2 +[v1.3.1]: https://github.com/SAP/ui5-builder/compare/v1.3.0...v1.3.1 +[v1.3.0]: https://github.com/SAP/ui5-builder/compare/v1.2.3...v1.3.0 +[v1.2.3]: https://github.com/SAP/ui5-builder/compare/v1.2.2...v1.2.3 +[v1.2.2]: https://github.com/SAP/ui5-builder/compare/v1.2.1...v1.2.2 +[v1.2.1]: https://github.com/SAP/ui5-builder/compare/v1.2.0...v1.2.1 +[v1.2.0]: https://github.com/SAP/ui5-builder/compare/v1.1.1...v1.2.0 +[v1.1.1]: https://github.com/SAP/ui5-builder/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/SAP/ui5-builder/compare/v1.0.5...v1.1.0 +[v1.0.5]: https://github.com/SAP/ui5-builder/compare/v1.0.4...v1.0.5 +[v1.0.4]: https://github.com/SAP/ui5-builder/compare/v1.0.3...v1.0.4 +[v1.0.3]: https://github.com/SAP/ui5-builder/compare/v1.0.2...v1.0.3 +[v1.0.2]: https://github.com/SAP/ui5-builder/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/SAP/ui5-builder/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/SAP/ui5-builder/compare/v0.2.9...v1.0.0 +[v0.2.9]: https://github.com/SAP/ui5-builder/compare/v0.2.8...v0.2.9 +[v0.2.8]: https://github.com/SAP/ui5-builder/compare/v0.2.7...v0.2.8 +[v0.2.7]: https://github.com/SAP/ui5-builder/compare/v0.2.6...v0.2.7 +[v0.2.6]: https://github.com/SAP/ui5-builder/compare/v0.2.5...v0.2.6 +[v0.2.5]: https://github.com/SAP/ui5-builder/compare/v0.2.4...v0.2.5 +[v0.2.4]: https://github.com/SAP/ui5-builder/compare/v0.2.3...v0.2.4 +[v0.2.3]: https://github.com/SAP/ui5-builder/compare/v0.2.2...v0.2.3 +[v0.2.2]: https://github.com/SAP/ui5-builder/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/SAP/ui5-builder/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/SAP/ui5-builder/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/SAP/ui5-builder/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/SAP/ui5-builder/compare/v0.0.2...v0.1.0 +[v0.0.2]: https://github.com/SAP/ui5-builder/compare/v0.0.1...v0.0.2 diff --git a/packages/builder/CONTRIBUTING.md b/packages/builder/CONTRIBUTING.md new file mode 100644 index 00000000000..2ddb6276ed5 --- /dev/null +++ b/packages/builder/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing to the UI5 CLI + +See CONTRIBUTING.md in the [UI5/cli](https://github.com/UI5/cli/blob/main/CONTRIBUTING.md) repository. diff --git a/packages/builder/LICENSE.txt b/packages/builder/LICENSE.txt new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/builder/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/builder/LICENSES/Apache-2.0.txt b/packages/builder/LICENSES/Apache-2.0.txt new file mode 100644 index 00000000000..4ed90b95224 --- /dev/null +++ b/packages/builder/LICENSES/Apache-2.0.txt @@ -0,0 +1,208 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, +AND DISTRIBUTION + + 1. Definitions. + + + +"License" shall mean the terms and conditions for use, reproduction, and distribution +as defined by Sections 1 through 9 of this document. + + + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + + + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct +or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more +of the outstanding shares, or (iii) beneficial ownership of such entity. + + + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions +granted by this License. + + + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + + + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + + + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that +is included in or attached to the work (an example is provided in the Appendix +below). + + + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative +Works shall not include works that remain separable from, or merely link (or +bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in +the Work by the copyright owner or by an individual or Legal Entity authorized +to submit on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + + + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently incorporated +within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and distribute +the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, +each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and otherwise +transfer the Work, where such license applies only to those patent claims +licensable by such Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work to which such +Contribution(s) was submitted. If You institute patent litigation against +any entity (including a cross-claim or counterclaim in a lawsuit) alleging +that the Work or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses granted to You +under this License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and +in Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source +form of the Work, excluding those notices that do not pertain to any part +of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable copy +of the attribution notices contained within such NOTICE file, excluding those +notices that do not pertain to any part of the Derivative Works, in at least +one of the following places: within a NOTICE text file distributed as part +of the Derivative Works; within the Source form or documentation, if provided +along with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text from the +Work, provided that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, +or distribution of Your modifications, or for any such Derivative Works as +a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without +any additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to +in writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR +A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness +of using or redistributing the Work and assume any risks associated with Your +exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether +in tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to +in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the possibility +of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work +or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such obligations, +You may act only on Your own behalf and on Your sole responsibility, not on +behalf of any other Contributor, and only if You agree to indemnify, defend, +and hold each Contributor harmless for any liability incurred by, or claims +asserted against, such Contributor by reason of your accepting any such warranty +or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own identifying +information. (Don't include the brackets!) The text should be enclosed in +the appropriate comment syntax for the file format. We also recommend that +a file or class name and description of purpose be included on the same "printed +page" as the copyright notice for easier identification within third-party +archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. diff --git a/packages/builder/README.md b/packages/builder/README.md new file mode 100644 index 00000000000..e912e83fa35 --- /dev/null +++ b/packages/builder/README.md @@ -0,0 +1,26 @@ +![UI5 icon](https://raw.githubusercontent.com/UI5/cli/main/docs/images/UI5_logo_wide.png) + +# ui5-builder +> Modules for building UI5 projects +> Part of the [UI5 CLI](https://github.com/UI5/cli) + +[![REUSE status](https://api.reuse.software/badge/github.com/SAP/ui5-builder)](https://api.reuse.software/info/github.com/SAP/ui5-builder) +[![Build Status](https://dev.azure.com/sap/opensource/_apis/build/status/SAP.ui5-builder?branchName=v4)](https://dev.azure.com/sap/opensource/_build/latest?definitionId=26&branchName=v4) +[![npm Package Version](https://badge.fury.io/js/%40ui5%2Fbuilder.svg)](https://www.npmjs.com/package/@ui5/builder) +[![Coverage Status](https://coveralls.io/repos/github/SAP/ui5-builder/badge.svg)](https://coveralls.io/github/SAP/ui5-builder) + +## Documentation +UI5 Builder documentation can be found here: [ui5.github.io/cli](https://ui5.github.io/cli/v4/pages/Builder/) + +The UI5 Builder API Reference can be found here: [`@ui5/builder`](https://ui5.github.io/cli/v4/api/) + +## Contributing +Please check our [Contribution Guidelines](https://github.com/UI5/cli/blob/main/CONTRIBUTING.md). + +## Support +Please follow our [Contribution Guidelines](https://github.com/UI5/cli/blob/main/CONTRIBUTING.md#report-an-issue) on how to report an issue. + +Please report issues in the main [UI5 CLI](https://github.com/UI5/cli) repository. + +## Release History +See [CHANGELOG.md](CHANGELOG.md). diff --git a/packages/builder/REUSE.toml b/packages/builder/REUSE.toml new file mode 100644 index 00000000000..745a5e18d6c --- /dev/null +++ b/packages/builder/REUSE.toml @@ -0,0 +1,17 @@ +version = 1 +SPDX-PackageName = "ui5-builder" +SPDX-PackageSupplier = "SAP OpenUI5 " +SPDX-PackageDownloadLocation = "https://github.com/SAP/ui5-builder" +SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project’s code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." + +[[annotations]] +path = "**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 SAP SE or an SAP affiliate company and UI5 CLI contributors" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = "lib/processors/jsdoc/lib/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 SAP SE or an SAP affiliate company and OpenUI5 contributors" +SPDX-License-Identifier = "Apache-2.0" diff --git a/packages/builder/azure-pipelines.yml b/packages/builder/azure-pipelines.yml new file mode 100644 index 00000000000..929b3342876 --- /dev/null +++ b/packages/builder/azure-pipelines.yml @@ -0,0 +1,81 @@ +# Node.js +# Build a general Node.js project with npm. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript + +trigger: +- v4 + +variables: + CI: true + +strategy: + matrix: + linux_node_lts_20_min_version: + imageName: 'ubuntu-24.04' + node_version: 20.11.0 + linux_node_22_min_version: + imageName: 'ubuntu-24.04' + node_version: 22.1.0 + linux_node_lts_20: + imageName: 'ubuntu-24.04' + node_version: 20.x + mac_node_lts_20: + imageName: 'macos-13' + node_version: 20.x + windows_node_lts_20: + imageName: 'windows-2022' + node_version: 20.x + linux_node_22: + imageName: 'ubuntu-24.04' + node_version: 22.x + mac_node_22: + imageName: 'macos-13' + node_version: 22.x + windows_node_22: + imageName: 'windows-2022' + node_version: 22.x + linux_node_24: + imageName: 'ubuntu-24.04' + node_version: 24.x + mac_node_24: + imageName: 'macos-13' + node_version: 24.x + windows_node_24: + imageName: 'windows-2022' + node_version: 24.x + +pool: + vmImage: $(imageName) + +steps: +- task: NodeTool@0 + inputs: + versionSpec: $(node_version) + displayName: Install Node.js + +- script: npm ci + displayName: Install Dependencies + +- script: npm ls --prod + displayName: Check for missing / extraneous Dependencies + +- script: npm run test-azure + displayName: Run Tests + +- task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(System.DefaultWorkingDirectory)/test-results.xml' + +- task: PublishCodeCoverageResults@2 + displayName: Publish Test Coverage Results + condition: succeededOrFailed() + inputs: + summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml' + +- script: npm run coverage + displayName: Run Test Natively in Case of Failures + condition: failed() diff --git a/packages/builder/eslint.common.config.js b/packages/builder/eslint.common.config.js new file mode 100644 index 00000000000..07876d526f0 --- /dev/null +++ b/packages/builder/eslint.common.config.js @@ -0,0 +1,99 @@ +import jsdoc from "eslint-plugin-jsdoc"; +import ava from "eslint-plugin-ava"; +import globals from "globals"; +import js from "@eslint/js"; +import google from "eslint-config-google"; + +export default [{ + ignores: [ // Common ignore patterns across all tooling repos + "**/coverage/", + "test/tmp/", + "test/expected/", + "test/fixtures/", + "**/docs/", + "**/jsdocs/", + ], +}, js.configs.recommended, google, ava.configs["flat/recommended"], { + name: "Common ESLint config used for all tooling repos", + + plugins: { + jsdoc, + }, + + languageOptions: { + globals: { + ...globals.node, + }, + + ecmaVersion: 2023, + sourceType: "module", + }, + + settings: { + jsdoc: { + mode: "jsdoc", + + tagNamePreference: { + return: "returns", + augments: "extends", + }, + }, + }, + + rules: { + "indent": ["error", "tab"], + "linebreak-style": ["error", "unix"], + + "quotes": ["error", "double", { + allowTemplateLiterals: true, + }], + + "semi": ["error", "always"], + "no-negated-condition": "off", + "require-jsdoc": "off", + "no-mixed-requires": "off", + + "max-len": ["error", { + code: 120, + ignoreUrls: true, + ignoreRegExpLiterals: true, + }], + + "no-implicit-coercion": [2, { + allow: ["!!"], + }], + + "comma-dangle": "off", + "no-tabs": "off", + "no-console": 2, // Disallow console.log() + "no-eval": 2, + // The following rule must be disabled as of ESLint 9. + // It's removed and causes issues when present + // https://eslint.org/docs/latest/rules/valid-jsdoc + "valid-jsdoc": 0, + "jsdoc/check-examples": 0, + "jsdoc/check-param-names": 2, + "jsdoc/check-tag-names": 2, + "jsdoc/check-types": 2, + "jsdoc/no-undefined-types": 0, + "jsdoc/require-description": 0, + "jsdoc/require-description-complete-sentence": 0, + "jsdoc/require-example": 0, + "jsdoc/require-hyphen-before-param-description": 0, + "jsdoc/require-param": 2, + "jsdoc/require-param-description": 0, + "jsdoc/require-param-name": 2, + "jsdoc/require-param-type": 2, + "jsdoc/require-returns": 0, + "jsdoc/require-returns-description": 0, + "jsdoc/require-returns-type": 2, + + "jsdoc/tag-lines": [2, "any", { + startLines: 1, + }], + + "jsdoc/valid-types": 0, + "ava/assertion-arguments": 0, + }, +} +]; diff --git a/packages/builder/eslint.config.js b/packages/builder/eslint.config.js new file mode 100644 index 00000000000..54ef6082a9f --- /dev/null +++ b/packages/builder/eslint.config.js @@ -0,0 +1,12 @@ +import eslintCommonConfig from "./eslint.common.config.js"; + +export default [ + ...eslintCommonConfig, // Load common ESLint config + { + // Add project-specific ESLint config rules here + // in order to override common config + ignores: [ + "lib/processors/jsdoc/lib", + ] + } +]; diff --git a/packages/builder/jsdoc-plugin.cjs b/packages/builder/jsdoc-plugin.cjs new file mode 100644 index 00000000000..cd7ef446d0f --- /dev/null +++ b/packages/builder/jsdoc-plugin.cjs @@ -0,0 +1,9 @@ +/* + * This plugin fixes unexpected JSDoc behavior that prevents us from using types that start with an at-sign (@). + * JSDoc doesn't see "{@" as a valid type expression, probably as there's also {@link ...}. + */ +exports.handlers = { + jsdocCommentFound: function(e) { + e.comment = e.comment.replace(/{@ui5\//g, "{ @ui5/"); + } +}; diff --git a/packages/builder/jsdoc.json b/packages/builder/jsdoc.json new file mode 100644 index 00000000000..54a0c51960d --- /dev/null +++ b/packages/builder/jsdoc.json @@ -0,0 +1,67 @@ +{ + "tags": { + "allowUnknownTags": false + }, + "source": { + "include": ["README.md"], + "exclude": [ + "lib/lbt/utils/JSTokenizer.js", + "lib/processors/jsdoc/lib/ui5/plugin.js", + "lib/processors/jsdoc/lib/ui5/template/publish.js", + "lib/processors/jsdoc/lib/ui5/template/utils/versionUtil.js" + ], + "includePattern": ".+\\.js$", + "excludePattern": "(node_modules(\\\\|/))" + }, + "plugins": [ + "./jsdoc-plugin.cjs" + ], + "opts": { + "encoding": "utf8", + "destination": "jsdocs/", + "recurse": true, + "verbose": true, + "access": "public" + }, + "templates": { + "cleverLinks": false, + "monospaceLinks": false, + "default": { + "useLongnameInNav": true + } + }, + "openGraph": { + "title": "UI5 CLI - API Reference", + "type": "website", + "image": "https://ui5.github.io/cli/v4/images/UI5_logo_wide.png", + "site_name": "UI5 CLI - API Reference", + "url": "https://ui5.github.io/cli/" + }, + "docdash": { + "sectionOrder": [ + "Modules", + "Namespaces", + "Classes", + "Externals", + "Events", + "Mixins", + "Tutorials", + "Interfaces" + ], + "meta": { + "title": "UI5 CLI - API Reference - UI5 Builder", + "description": "UI5 CLI - API Reference - UI5 Builder", + "keyword": "openui5 sapui5 ui5 build development tool api reference" + }, + "search": true, + "wrap": true, + "menu": { + "GitHub": { + "href": "https://github.com/SAP/ui5-builder", + "target": "_blank", + "class": "menu-item", + "id": "github_link" + } + } + } +} diff --git a/packages/builder/lib/lbt/UI5ClientConstants.js b/packages/builder/lib/lbt/UI5ClientConstants.js new file mode 100644 index 00000000000..c6bdf20176c --- /dev/null +++ b/packages/builder/lib/lbt/UI5ClientConstants.js @@ -0,0 +1,12 @@ + +export const MODULE__UI5LOADER = "ui5loader.js"; +export const MODULE__UI5LOADER_AUTOCONFIG = "ui5loader-autoconfig.js"; +export const MODULE__JQUERY_SAP_GLOBAL = "jquery.sap.global.js"; +export const MODULE__SAP_UI_CORE_CORE = "sap/ui/core/Core.js"; +export const EVO_MARKER_RESOURCE = MODULE__UI5LOADER; + +export function getRendererName( module ) { + if ( /\.js$/.test(module) ) { + return module.replace(/\.js$/, "Renderer.js"); + } +} diff --git a/packages/builder/lib/lbt/analyzer/ComponentAnalyzer.js b/packages/builder/lib/lbt/analyzer/ComponentAnalyzer.js new file mode 100644 index 00000000000..1f32bcb08c4 --- /dev/null +++ b/packages/builder/lib/lbt/analyzer/ComponentAnalyzer.js @@ -0,0 +1,251 @@ +/** + * Analyzes a UI5 Component to collect dependency information. + * + * Tries to find a manifest.json in the same package. If it is found and if + * it is a valid JSON, an "sap.ui5" section is searched and evaluated in the following way + * - any library dependency is added as a dependency to the library.js module + * of that library. If the library dependency is modelled as 'lazy', the + * module dependency will be added as 'conditional' + * - any component dependency is added as a dependency to the Component.js module + * of that component. If the Component dependency is modeled as 'lazy', the + * module dependency will be added as 'conditional' + * - for each configured UI5 module for which a type is configured, a module + * dependency to that type is added + * - for each route that contains a view name, a module dependency to that view will be + * added + * TODO component usages have to be handled + * + * This class can handle multiple concurrent analysis calls, it has no instance state + * other than the pool (which is readonly). + */ + +import {fromUI5LegacyName} from "../utils/ModuleName.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:analyzer:ComponentAnalyzer"); + +// --------------------------------------------------------------------------------------------------------- + +function each(obj, fn) { + if ( obj ) { + Object.keys(obj).forEach( + (key) => fn(obj[key], key, obj) + ); + } +} + +/** + * Analyzes the manifest for a Component.js to find more dependencies. + * + * @since 1.47.0 + * @private + */ +class ComponentAnalyzer { + constructor(pool) { + this._pool = pool; + } + + async analyze(resource, info) { + // ignore base class for components + if ( resource.name === "sap/ui/core/Component.js" ) { + return info; + } + + const manifestName = resource.name.replace(/Component\.js$/, "manifest.json"); + try { + const manifestResource = await this._pool.findResource(manifestName).catch(() => null); + if ( manifestResource ) { + const fileContent = await manifestResource.buffer(); + this._analyzeManifest( JSON.parse(fileContent.toString()), info ); + } else { + log.verbose(`No manifest found for '${resource.name}', skipping analysis`); + } + } catch (err) { + log.error(`An error occurred while analyzing component ${resource.name} (ignored): ${err.message}`); + log.verbose(err.stack); + } + + return info; + } + + _getRootViewModule(rootView) { + // For runtime logic see sap/ui/core/UIComponent#createContent and sap/ui/core/mvc/View#_getModuleName + + if (typeof rootView === "string") { + rootView = { + viewName: rootView + }; + } + if (!rootView.type) { + rootView.type = "XML"; + } + + if (!rootView.viewName) { + log.warn(`Unable to analyze sap.ui5/rootView: Missing viewName`); + return null; + } + + if (rootView.viewName.startsWith("module:")) { + return rootView.viewName.slice("module:".length) + ".js"; + } + + let viewTypeExtension; + + switch (rootView.type) { + case "JS": + viewTypeExtension = ".view.js"; + break; + case "JSON": + viewTypeExtension = ".view.json"; + break; + case "Template": + viewTypeExtension = ".view.tmpl"; + break; + case "XML": + viewTypeExtension = ".view.xml"; + break; + case "HTML": + viewTypeExtension = ".view.html"; + break; + default: + log.warn(`Unable to analyze sap.ui5/rootView: Unknown type '${rootView.type}'`); + return null; + } + + return fromUI5LegacyName(rootView.viewName, viewTypeExtension); + } + + /** + * Evaluates a manifest after it has been read and parsed + * and adds any newly found dependencies to the given info object. + * + * @param {object} manifest JSON with app descriptor structure + * @param {ModuleInfo} info ModuleInfo object that should be enriched + * @returns {ModuleInfo} ModuleInfo object that should be enriched + * @private + */ + _analyzeManifest( manifest, info ) { + const sapApp = (manifest && manifest["sap.app"]) || {}; + const ui5 = (manifest && manifest["sap.ui5"]) || {}; + + if ( ui5.resources && ui5.resources.css ) { + // TODO how to handle CSS dependencies? + } + + if ( ui5.resources && ui5.resources.js ) { + // TODO how to handle JS dependencies (they are URLs, not module names) + } + + if ( ui5.rootView ) { + const module = this._getRootViewModule(ui5.rootView); + if (module) { + log.verbose(`Adding root view dependency ${module}`); + info.addDependency( module ); + } + } + + each( ui5.dependencies && ui5.dependencies.libs, (options, lib) => { + const module = fromUI5LegacyName(lib, "/library.js"); + log.verbose(`Adding library dependency ${module} - lazy: ${options.lazy || false}`); + info.addDependency( module, options.lazy ); // lazy -> conditional dependency + }); + + each( ui5.dependencies && ui5.dependencies.components, (options, component) => { + const module = fromUI5LegacyName(component, "/Component.js"); + log.verbose(`Adding component dependency ${module} - lazy: ${options.lazy || false}`); + info.addDependency( module, options.lazy ); // lazy -> conditional dependency + }); + + // TODO usages + + // See sap/ui/core/Component._createManifestModelConfigurations + each( ui5.models, (options, model) => { + let modelType; + if ( options.type ) { + modelType = options.type; + } else if ( options.dataSource ) { + const oDataSource = sapApp.dataSources && sapApp.dataSources[options.dataSource]; + if (!oDataSource) { + log.warn(`Provided dataSource "${options.dataSource}" for model "${model}" does not exist.`); + return; + } + // default dataSource type is OData + const dataSourceType = oDataSource.type || "OData"; + let odataVersion; + switch (dataSourceType) { + case "OData": + odataVersion = oDataSource.settings && oDataSource.settings.odataVersion; + if (odataVersion === "4.0") { + modelType = "sap.ui.model.odata.v4.ODataModel"; + } else if (!odataVersion || odataVersion === "2.0") { + // Default if no version is specified + modelType = "sap.ui.model.odata.v2.ODataModel"; + } else { + log.warn(`Provided OData version "${odataVersion}" in ` + + `dataSource "${options.dataSource}" for model "${model}" is unknown. ` + + `You might be using an outdated version of the UI5 CLI.`); + return; + } + break; + case "JSON": + modelType = "sap.ui.model.json.JSONModel"; + break; + case "XML": + modelType = "sap.ui.model.xml.XMLModel"; + break; + default: + // for custom dataSource types, the class should already be specified in the sap.ui5 models config + log.warn(`Unknown dataSource type "${dataSourceType}" defined for model "${model}". ` + + `Please configure a "type" in the model config.`); + return; + } + } else { + log.warn(`Neither a type nor a dataSource has been defined for model "${model}".`); + return; + } + const module = fromUI5LegacyName( modelType ); + log.verbose(`Derived model implementation dependency ${module}`); + info.addDependency(module); + }); + + const routing = ui5.routing; + if ( routing ) { + const routingConfig = routing.config || {}; + + // See sap/ui/core/UIComponent#init + if (routing.routes) { + const routerClassName = routingConfig.routerClass || "sap.ui.core.routing.Router"; + const routerClassModule = fromUI5LegacyName(routerClassName); + log.verbose(`Adding router dependency '${routerClassModule}'`); + info.addDependency(routerClassModule); + } else if (routing.targets) { + const targetsModule = routingConfig.targetsClass || "sap/ui/core/routing/Targets.js"; + log.verbose(`Adding routing targets dependency '${targetsModule}'`); + info.addDependency(targetsModule); + + const viewsModule = "sap/ui/core/routing/Views.js"; + log.verbose(`Adding routing views dependency '${viewsModule}'`); + info.addDependency(viewsModule); + } + + if (routing.targets) { + for (const targetName of Object.keys(routing.targets)) { + const target = routing.targets[targetName]; + if (target && target.viewName) { + // merge target config with default config + const config = Object.assign({}, routing.config, target); + const module = fromUI5LegacyName( + (config.viewPath ? config.viewPath + "." : "") + + config.viewName, ".view." + config.viewType.toLowerCase() ); + log.verbose(`Converting routing target '${targetName}' to view dependency '${module}'`); + // TODO make this a conditional dependency, depending on the route pattern? + info.addDependency(module); + } + } + } + } + + return info; + } +} + +export default ComponentAnalyzer; diff --git a/packages/builder/lib/lbt/analyzer/JSModuleAnalyzer.js b/packages/builder/lib/lbt/analyzer/JSModuleAnalyzer.js new file mode 100644 index 00000000000..edb506cafd7 --- /dev/null +++ b/packages/builder/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -0,0 +1,799 @@ +import {Syntax, VisitorKeys} from "../utils/parseUtils.js"; +import escope from "escope"; +import {fromUI5LegacyName, fromRequireJSName, resolveRelativeRequireJSName} from "../utils/ModuleName.js"; +import moduleInfo from "../resources/ModuleInfo.js"; +const ModuleFormat = moduleInfo.Format; +import {MODULE__JQUERY_SAP_GLOBAL, MODULE__UI5LOADER_AUTOCONFIG} from "../UI5ClientConstants.js"; +import { + findOwnProperty, + getLocation, + getPropertyKey, + isMethodCall, + isString, + getStringValue, +} from "../utils/ASTUtils.js"; + +import {getLogger} from "@ui5/logger"; + +const log = getLogger("lbt:analyzer:JSModuleAnalyzer"); + +// ------------------------------------------------------------------------------------------------------------------ + +export const EnrichedVisitorKeys = (function() { + function toBeDone() { + return null; + } + + /* + * The following object contains for each known estree node type + * a list of visitor keys that represent conditionally executed code branches. + * E.g. in an IfExpression, the 'test' is always executed, whereas 'consequent' + * and 'alternate' are only executed under certain conditions. + * + * While visiting the AST of a JavaScript file, the JSModuleAnalyzer uses this information + * to decide whether a code block is executed conditionally or unconditionally. + * Besides this information which is inherent to the language, the analyzer uses + * additional knowledge about special APIS / constructs (e.g. the factory function of + * an AMD module is known to be executed when the module is executed, an IIFE is known to + * be executed etc.) + * + * To be more robust against the evolution of the language, the object below is checked + * against the 'official' list of node types and node keys as defined by 'estraverse'. + * This helps to ensure that no new syntax addition is missed and that the configured + * keys are valid. + */ + const TempKeys = { + AssignmentExpression: [], + /* + * function( >>>a=3<<<, b) {...} + * var [>>>a=3<<<, b] = [...]; + * + * The default value expression (right) is only evaluated when there's no other value in + * the context of the pattern (e.g. destructuring or function call don't provide a value), + * so it's a conditional branch. + */ + AssignmentPattern: ["right"], + ArrayExpression: [], + /* + * var >>>[a=3, b]<<< = [...]; + * All elements in an array pattern are unconditional. + */ + ArrayPattern: [], // elements + /* + * The body of an arrow function is only executed when the arrow function is executed + */ + ArrowFunctionExpression: ["body"], + /* + * The argument of await is always executed + * TODO how to handle code after the await expression? + */ + AwaitExpression: [], // argument + BlockStatement: [], + BinaryExpression: [], + BreakStatement: [], + CallExpression: [], // special handling + CatchClause: ["param", "body"], + ChainExpression: ["expression"], + ClassBody: [], + ClassDeclaration: [], + ClassExpression: [], + // ComprehensionBlock: toBeDone(["left", "right"]), // CAUTION: It's deferred to ES7. + // ComprehensionExpression: toBeDone(), // CAUTION: It's deferred to ES7. + ConditionalExpression: ["consequent", "alternate"], + ContinueStatement: [], + DebuggerStatement: [], + /* + * 'condition' is executed on the same conditions as the surrounding block, potentially repeated, + * 'block' is always entered and might be repeated + */ + DoWhileStatement: [], + EmptyStatement: [], + ExportAllDeclaration: [], // no parts of an export are conditional - source + ExportDefaultDeclaration: [], // no parts of an export are conditional - declaration + ExportNamedDeclaration: [], // no parts of an export are conditional - declaration, specifiers, source + ExportSpecifier: [], // no parts of an export are conditional exported, local + ExpressionStatement: [], + ForStatement: ["update", "body"], + ForInStatement: ["body"], + ForOfStatement: ["body"], + FunctionDeclaration: ["body"], // a nested function is potentially 'conditional' + FunctionExpression: ["body"], // a nested function is potentially 'conditional' + // GeneratorExpression: toBeDone(["blocks", "filter", "body"]), // CAUTION: It's deferred to ES7. + Identifier: [], + IfStatement: ["consequent", "alternate"], + /* + * all parts of an import declaration are executed unconditionally + */ + ImportDeclaration: [], // specifiers, source + /* + * import >>>a<<< from 'module'; + */ + ImportDefaultSpecifier: [], // local + /* + * Dynamic Import expression, the argument is evaluated unconditionally. + */ + ImportExpression: [], // source, + /* + * import >>>* as b<<< from 'module'; + */ + ImportNamespaceSpecifier: [], // local + /* + * import {>>>a as c<<<,b} from 'module'; + */ + ImportSpecifier: [], // imported, local + Literal: [], + LabeledStatement: [], + LogicalExpression: ["right"], + MemberExpression: [], + MetaProperty: toBeDone(["meta", "property"]), + MethodDefinition: [], + NewExpression: [], + ObjectExpression: [], + /* + * >>>{a,b,c}<<< = {...} + * + * All properties in an object pattern are executed. + */ + ObjectPattern: [], // properties + PrivateIdentifier: [], + Program: [], + Property: [], + PropertyDefinition: [], + /* + * argument of the rest element is always executed under the same condition as the rest element itself + */ + RestElement: [], // argument + ReturnStatement: [], + SequenceExpression: [], + SpreadElement: [], // the argument of the spread operator always needs to be evaluated - argument + StaticBlock: [], + Super: [], + SwitchStatement: [], + SwitchCase: ["test", "consequent"], // test and consequent are executed only conditionally + /* + * all parts of a tagged template literal are executed under the same condition as the context + */ + TaggedTemplateExpression: [], // tag, quasi + TemplateElement: [], + /* + * all parts of a template literal are executed under the same condition as the context + */ + TemplateLiteral: [], // quasis, expressions + ThisExpression: [], + ThrowStatement: [], + TryStatement: ["handler"], // handler is called conditionally + UnaryExpression: [], + UpdateExpression: [], + VariableDeclaration: [], + VariableDeclarator: [], + /* + * 'condition' is executed on the same conditions as the surrounding block and potentially repeated, + * 'block' maybe entered only conditionally but can be repeated + */ + WhileStatement: ["body"], + WithStatement: [], + YieldExpression: [] + }; + + // check for unknown keys in our configuration + for ( const type in TempKeys ) { + if ( VisitorKeys[type] === undefined ) { + throw new Error(`Configuration contains unknown node type '${type}'`); + } + } + + // merge with 'official' visitor keys + Object.keys(VisitorKeys).forEach( (type) => { + const visitorKeys = VisitorKeys[type]; + const condKeys = TempKeys[type]; + if ( Array.isArray(condKeys) ) { + // check configured keys against visitor keys + condKeys.forEach( (key) => { + if ( !visitorKeys.includes(key) ) { + throw new Error(`Configuration for type '${type}' contains unknown key '${key}'`); + } + }); + TempKeys[type] = visitorKeys.map( (key) => ({ + key: key, + conditional: condKeys.includes(key) + }) ); + } else if (condKeys === null) { + // this is a 'toBeDone' node type, keep null and complain at runtime when such a node occurs + } else { + // undefined => ignored node type (see JSModuleAnalyzer consistency test) + } + }); + + return TempKeys; +}()); + + +const CALL_AMD_DEFINE = ["define"]; +const CALL_AMD_REQUIRE = ["require"]; +const CALL_REQUIRE_SYNC = ["require", "sync"]; +const CALL_REQUIRE_PREDEFINE = ["require", "predefine"]; +const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"]; +const CALL_SAP_UI_REQUIRE = ["sap", "ui", "require"]; +const CALL_SAP_UI_REQUIRE_SYNC = ["sap", "ui", "requireSync"]; +const CALL_SAP_UI_REQUIRE_PRELOAD = ["sap", "ui", "require", "preload"]; +const CALL_SAP_UI_PREDEFINE = ["sap", "ui", "predefine"]; +const CALL_JQUERY_SAP_DECLARE = [["jQuery", "$"], "sap", "declare"]; +const CALL_JQUERY_SAP_IS_DECLARED = [["jQuery", "$"], "sap", "isDeclared"]; +const CALL_JQUERY_SAP_REQUIRE = [["jQuery", "$"], "sap", "require"]; +const CALL_JQUERY_SAP_REGISTER_PRELOADED_MODULES = [["jQuery", "$"], "sap", "registerPreloadedModules"]; +const SPECIAL_AMD_DEPENDENCIES = ["require", "exports", "module"]; + + +function isCallableExpression(node) { + return node.type == Syntax.FunctionExpression || node.type == Syntax.ArrowFunctionExpression; +} + +/* + * Dummy implementation. + * Sole purpose is to easier align with the old (Java) implementation of the bundle tooling. + */ +function getDocumentation(node) { + return undefined; +} + +/** + * Analyzes an already parsed JSDocument to collect information about the contained module(s). + * + * Can handle jQuery.sap.require/jQuery.sap.declare/sap.ui.define and jquery.sap.isDeclared calls. + * + * @author Frank Weigel + * @since 1.1.2 + * @private + */ +class JSModuleAnalyzer { + /** + * Analyzes the JS AST + * + * @param {object} ast js ast + * @param {string} defaultName default name + * @param {ModuleInfo} info module info + */ + analyze(ast, defaultName, info) { + let mainModuleFound = false; + /** + * Number of (sap.ui.)define calls without a module ID. + * Only tracked to be able to complain about multiple module definitions without ID. + */ + let nUnnamedDefines = 0; + /** + * ID of the first named (sap.ui.)define call. + * Remembered together with the corresponding description in case no other main module + * can be found (no unnamed module, no module with the ID that matches the filename). + * Will be used as main module ID if only one module definition exists in the file. + */ + let candidateName = null; + let candidateDescription = null; + + /** + * Total number of module declarations (declare or define). + */ + let nModuleDeclarations = 0; + + /** + * Whether or not this is a UI5 module + * + * When in the non-conditional module execution there is a call to: + * + * this value is true + * + * @type {boolean} + */ + let bIsUi5Module = false; + + // Module name via @ui5-bundle comment in first line. Overrides all other main module candidates. + let firstLineBundleName; + + // first analyze the whole AST... + visit(ast, false); + + // ...then all the comments + if ( Array.isArray(ast.comments) ) { + ast.comments.forEach((comment) => { + if ( comment.type === "Line" && comment.value.startsWith("@ui5-bundle") ) { + if ( comment.value.startsWith("@ui5-bundle-raw-include ") ) { + const subModule = comment.value.slice("@ui5-bundle-raw-include ".length); + info.addSubModule(subModule); + log.verbose(`Bundle include directive ${subModule}`); + } else if ( comment.value.startsWith("@ui5-bundle ") ) { + const bundleName = comment.value.slice("@ui5-bundle ".length); + if (comment.start === 0) { + // Remember the name from the first line to use it as final name + firstLineBundleName = bundleName; + } else { + setMainModuleInfo(bundleName, null); + } + log.verbose(`Bundle name directive ${bundleName}`); + } else { + log.warn(`Unrecognized bundle directive ${comment.value}`); + } + } + }); + } + + // ...and finally take conclusions about the file's content + if ( firstLineBundleName ) { + // If the first line has a bundle name, use it and override all other found names + info.name = firstLineBundleName; + } else if ( !mainModuleFound ) { + // if there's exactly one module definition in this file but it didn't + // immediately qualify as main module, make it now the main module + if ( candidateName != null && nModuleDeclarations == 1 ) { + info.name = candidateName; + info.description = candidateDescription; + mainModuleFound = true; + } else { + // no main module found, use the default name + info.name = defaultName; + } + } + + // depending on the used module APIs, add an implicit dependency to the loader entry module + if ( info.format === ModuleFormat.UI5_LEGACY ) { + info.addImplicitDependency(MODULE__JQUERY_SAP_GLOBAL); + } else if ( info.format === ModuleFormat.UI5_DEFINE ) { + // Note: the implicit dependency for sap.ui.define modules points to the standard UI5 + // loader config module. A more general approach would be to add a dependency to the loader + // only, but then standard configuration would be missed by dependency resolution + // (to be clarified) + info.addImplicitDependency(MODULE__UI5LOADER_AUTOCONFIG); + } + + if ( !bIsUi5Module ) { + // when there are no indicators for module APIs, mark the module as 'raw' module + info.rawModule = true; + } + + const scopeManager = escope.analyze(ast); + const currentScope = scopeManager.acquire(ast); // global scope + if ( currentScope.set.size > 0 ) { + info.requiresTopLevelScope = true; + info.exposedGlobals = Array.from(currentScope.set.keys()); + // console.log(info.name, "exposed globals", info.exposedGlobals, "ignoredGlobals", info.ignoredGlobals); + } + + + // hoisted functions + function setMainModuleInfo(name, description) { + if ( mainModuleFound ) { + throw new Error("Conflicting main modules found (unnamed + named)"); + } + mainModuleFound = true; + info.name = name; + if ( description != null ) { + info.description = description; + } + } + + function visit(node, conditional) { + // console.log("visiting ", node); + + if ( node == null ) { + return; + } + + if ( Array.isArray(node) ) { + node.forEach((child) => visit(child, conditional)); + return; + } + + const condKeys = EnrichedVisitorKeys[node.type]; + switch (node.type) { + case Syntax.CallExpression: + if ( !conditional && isMethodCall(node, CALL_JQUERY_SAP_DECLARE) ) { + // recognized a call to jQuery.sap.declare() + nModuleDeclarations++; + info.setFormat(ModuleFormat.UI5_LEGACY); + bIsUi5Module = true; + onDeclare(node); + } else if ( !conditional && + (isMethodCall(node, CALL_SAP_UI_DEFINE) || isMethodCall(node, CALL_AMD_DEFINE)) ) { + // recognized a call to define() or sap.ui.define() + // console.log("**** recognized a call to sap.ui.define"); + nModuleDeclarations++; + if ( isMethodCall(node, CALL_SAP_UI_DEFINE) ) { + info.setFormat(ModuleFormat.UI5_DEFINE); + } else { + info.setFormat(ModuleFormat.AMD); + } + bIsUi5Module = true; + onDefine(node); + + const args = node.arguments; + let iArg = 0; + if ( iArg < args.length && isString(args[iArg]) ) { + iArg++; + } + if ( iArg < args.length && args[iArg].type == Syntax.ArrayExpression ) { + iArg++; + } + if ( iArg < args.length && isCallableExpression(args[iArg]) ) { + // unconditionally execute the factory function + visit(args[iArg].body, conditional); + } + } else if ( isMethodCall(node, CALL_REQUIRE_PREDEFINE) || isMethodCall(node, CALL_SAP_UI_PREDEFINE) ) { + // recognized a call to require.predefine() or sap.ui.predefine() + if (!conditional) { + bIsUi5Module = true; + } + info.setFormat(ModuleFormat.UI5_DEFINE); + onSapUiPredefine(node, conditional); + + const args = node.arguments; + let iArg = 0; + if ( iArg < args.length && isString(args[iArg]) ) { + iArg++; + } + if ( iArg < args.length && args[iArg].type == Syntax.ArrayExpression ) { + iArg++; + } + if ( iArg < args.length && isCallableExpression(args[iArg]) ) { + // unconditionally execute the factory function + visit(args[iArg].body, conditional); + } + } else if ( isMethodCall(node, CALL_SAP_UI_REQUIRE) || isMethodCall(node, CALL_AMD_REQUIRE) ) { + // recognized a call to require() or sap.ui.require() + if ( isMethodCall(node, CALL_SAP_UI_REQUIRE) ) { + info.setFormat(ModuleFormat.UI5_DEFINE); + } else { + info.setFormat(ModuleFormat.AMD); + } + let iArg = 0; + const args = node.arguments; + if ( iArg < args.length && args[iArg].type == Syntax.ArrayExpression ) { + // TODO onAsyncRequire(node, node.getChild(1)); + // requireJS signature, handle as such + analyzeDependencyArray(args[iArg].elements, conditional, null); + iArg++; + } + if ( iArg < args.length && isCallableExpression(args[iArg]) ) { + // analyze the callback function + visit(args[iArg].body, conditional); + } + } else if ( isMethodCall(node, CALL_REQUIRE_SYNC) || isMethodCall(node, CALL_SAP_UI_REQUIRE_SYNC) ) { + // recognizes a call to sap.ui.requireSync + info.setFormat(ModuleFormat.UI5_DEFINE); + + onSapUiRequireSync(node, conditional); + } else if ( isMethodCall(node, CALL_JQUERY_SAP_REQUIRE) ) { + // recognizes a call to jQuery.sap.require + info.setFormat(ModuleFormat.UI5_LEGACY); + onJQuerySapRequire(node, conditional); + } else if ( isMethodCall(node, CALL_JQUERY_SAP_REGISTER_PRELOADED_MODULES) ) { + // recognizes a call to jQuery.sap.registerPreloadedModules + if (!conditional) { + bIsUi5Module = true; + } + info.setFormat(ModuleFormat.UI5_LEGACY); + onRegisterPreloadedModules(node, /* evoSyntax= */ false); + } else if ( isMethodCall(node, CALL_SAP_UI_REQUIRE_PRELOAD) ) { + // recognizes a call to sap.ui.require.preload + if (!conditional) { + bIsUi5Module = true; + } + info.setFormat(ModuleFormat.UI5_DEFINE); + onRegisterPreloadedModules(node, /* evoSyntax= */ true); + } else if ( isCallableExpression(node.callee) ) { + // recognizes a scope function declaration + argument + visit(node.arguments, conditional); + // NODE-TODO defaults of callee? + visit(node.callee.body, conditional); + } else { + // default visit + for ( const key of condKeys ) { + visit(node[key.key], key.conditional || conditional); + } + } + break; + + case Syntax.IfStatement: + // recognizes blocks of the form + // if ( !jQuery.sap.isDeclared() ) { + // ... + // } + // required for the analysis of files that have been build with the + // embedding merge writer (e.g. sap-ui-core-all.js) + if ( node.test.type == Syntax.UnaryExpression && + node.test.operator === "!" && + isMethodCall(node.test.argument, CALL_JQUERY_SAP_IS_DECLARED ) ) { + visit(node.consequent, conditional); + visit(node.alternate, true); + } else { + // default visit + for ( const key of condKeys ) { + visit(node[key.key], key.conditional || conditional); + } + } + break; + + case Syntax.PropertyDefinition: + + // Instance properties (static=false) are only initialized when an instance is created (conditional) + // but a computed key is always evaluated on class initialization (eager) + visit(node.key, conditional); + visit(node.value, !node.static || conditional); + + break; + + default: + if ( condKeys == null ) { + log.error(`Unhandled AST node type ${node.type}`, node); + throw new Error(`Unhandled AST node type ${node.type}`); + } + // default visit + for ( const key of condKeys ) { + visit(node[key.key], key.conditional || conditional); + } + break; + } + } + + function onDeclare(node) { + const args = node.arguments; + if (args.length > 0) { + const value = getStringValue(args[0]); + if (value !== undefined) { + const name = fromUI5LegacyName(value); + if ( nModuleDeclarations === 1 && !mainModuleFound) { + // if this is the first declaration, then this is the main module declaration + // note that this overrides an already given name + setMainModuleInfo(name, getDocumentation(node)); + } else if ( nModuleDeclarations > 1 && name === info.name ) { + // ignore duplicate declarations (e.g. in behavior file of design time controls) + log.warn(`Duplicate declaration of module name at ${getLocation(args)} in ${name}`); + } else { + // otherwise it is just a submodule declaration + info.addSubModule(name); + } + return; + } else { + log.error( + `jQuery.sap.declare: Module name could not be determined from first argument: ${args[0]}`); + } + } else { + log.error("jQuery.sap.declare: Module name could not be determined, no arguments are given"); + } + } + + function onDefine(defineCall) { + const args = defineCall.arguments; + const nArgs = args.length; + let i = 0; + + // get the documentation from a preceding comment + const desc = getDocumentation(defineCall); + + // determine the name of the module + let name = null; + if ( i < nArgs ) { + const value = getStringValue( args[i] ); + if ( value !== undefined ) { + name = fromRequireJSName(value); + if ( name === defaultName ) { + // hardcoded name equals the file name, so this definition qualifies as main module definition + setMainModuleInfo(name, desc); + } else { + info.addSubModule(name); + if ( candidateName == null ) { + // remember the name and description in case no other module qualifies as main module + candidateName = name; + candidateDescription = desc; + } + } + i++; + } + } + + if ( !name ) { + nUnnamedDefines++; + if ( nUnnamedDefines > 1 ) { + throw new Error( + "If multiple modules are contained in a file, only one of them may omit the module ID " + + name + " " + nUnnamedDefines); + } + if ( defaultName == null ) { + throw new Error("Unnamed module found, but no default name given"); + } + name = defaultName; + // the first unnamed module definition qualifies as main module + setMainModuleInfo(name, desc); + } + + // process array of required modules, if given + if ( i < nArgs && args[i].type === Syntax.ArrayExpression ) { + analyzeDependencyArray(args[i].elements, false, name); // TODO not always false, depends on context? + i++; + } + } + + function onJQuerySapRequire(requireCall, conditional) { + const args = requireCall.arguments; + const nArgs = args.length; + + if ( nArgs > 0 && args[0].type == Syntax.OBJECT ) { + log.verbose("jQuery.sap.require: Cannot evaluate complex require (view/controller)"); + } else { + // UI5 signature with one or many required modules + for (let i = 0; i < nArgs; i++) { + const arg = args[i]; + const value = getStringValue(arg); + if ( value !== undefined ) { + const requiredModuleName = fromUI5LegacyName( value ); + info.addDependency(requiredModuleName, conditional); + } else if ( arg.type == Syntax.ConditionalExpression) { + const consequentValue = getStringValue(arg.consequent); + const alternateValue = getStringValue(arg.alternate); + if ( consequentValue !== undefined ) { + const requiredModuleName1 = fromUI5LegacyName( consequentValue ); + info.addDependency(requiredModuleName1, true); + } + if ( alternateValue !== undefined ) { + const requiredModuleName2 = fromUI5LegacyName( alternateValue ); + info.addDependency(requiredModuleName2, true); + } + if ( consequentValue === undefined || alternateValue === undefined ) { + log.verbose(`jQuery.sap.require: Cannot evaluate dynamic arguments: ${arg && arg.type}`); + info.dynamicDependencies = true; + } + } else { + log.verbose(`jQuery.sap.require: Cannot evaluate dynamic arguments: ${arg && arg.type}`); + info.dynamicDependencies = true; + } + } + } + } + + function onSapUiRequireSync(node, conditional) { + const args = node.arguments; + const nArgs = args.length; + const i = 0; + + if ( i < nArgs ) { + const value = getStringValue(args[i]); + if ( value !== undefined ) { + // sap.ui.requireSync does not support relative dependencies + const moduleName = fromRequireJSName( value ); + info.addDependency(moduleName, conditional); + } else { + log.verbose(`sap.ui.requireSync: Cannot evaluate dynamic arguments: ${args[i] && args[i].type}`); + info.dynamicDependencies = true; + } + } + } + + function onSapUiPredefine(predefineCall, conditional) { + const args = predefineCall.arguments; + const nArgs = args.length; + let i = 0; + + // determine the name of the module + if ( i < nArgs ) { + const value = getStringValue(args[i++]); + if ( value !== undefined ) { + const moduleName = fromRequireJSName( value ); + info.addSubModule(moduleName); + + // add dependencies + // to correctly identify dependencies e.g. of a library-preload + const elementArg = args[i++]; + if (elementArg && elementArg.type === Syntax.ArrayExpression) { + elementArg.elements.forEach((element) => { + const dependencyName = resolveRelativeRequireJSName(moduleName, + getStringValue(element)); + info.addDependency(dependencyName, conditional); + }); + } + } else { + log.warn("sap.ui.predefine call has a non supported type for module name (ignored)"); + } + } else { + log.warn("sap.ui.predefine call is missing a module name (ignored)"); + } + } + + function onRegisterPreloadedModules(node, evoSyntax) { + const args = node.arguments; + + // trace.debug("**** registerPreloadedModules detected"); + let modules = null; + let namesUseLegacyNotation = false; + + if ( evoSyntax ) { + modules = args[0]; + } else { + const obj = args[0]; + if (obj && obj.type === Syntax.ObjectExpression) { + const version = findOwnProperty(obj, "version"); + namesUseLegacyNotation = !(version && isString(version) && parseFloat(version.value) >= 2.0); + modules = findOwnProperty(obj, "modules"); + } + } + if ( modules && modules.type == Syntax.ObjectExpression ) { + modules.properties.forEach( function(property) { + let moduleName = getPropertyKey(property); + if ( !moduleName ) { + return; + } + if ( namesUseLegacyNotation ) { + moduleName = fromUI5LegacyName(moduleName); + } + info.addSubModule(moduleName); + }); + } else { + log.verbose(`Cannot evaluate registerPreloadedModules: ${modules && modules.type}`); + } + } + + function analyzeDependencyArray(array, conditional, name) { + // console.log(array); + array.forEach( (item) => { + const value = getStringValue(item); + if ( value !== undefined ) { + // ignore special AMD dependencies (require, exports, module) + if ( SPECIAL_AMD_DEPENDENCIES.includes(value) ) { + return; + } + let requiredModule; + if (name == null) { + requiredModule = fromRequireJSName( value ); + } else { + requiredModule = resolveRelativeRequireJSName(name, value); + } + info.addDependency( requiredModule, conditional ); + } else { + log.verbose(`sap.ui.require/sap.ui.define: Cannot evaluate dynamic argument: ${item && item.type}`); + info.dynamicDependencies = true; + } + }); + } + + /* + private static final boolean isRealWhitespace(Token t) { + return t.type == Syntax.WhiteSpace || t.type == Syntax.EOL; + } + + private String getDocumentation(Tree node) { + int afterWS = node.getTokenStartIndex(); + + // ignore any real whitespace (WS or EOL) + while ( afterWS > 0 && isRealWhitespace(doc.getToken(afterWS-1)) ) + afterWS--; + + if (afterWS > 0) { + if ( doc.getToken(afterWS-1).type == Syntax.MultiLineComment ) { + Token t = doc.getToken(afterWS-1); + String s = t.getText(); + s = s.replaceFirst("^/\\*[ \t*]*", ""); + s = s.replaceAll("[ \t*]*\\* /$", ""); + s = s.replaceAll("[\r\n]+[ \t]*\\*+[ \t]*", " "); + s = s.trim(); + return s; + } else { + int firstWS = afterWS; + while ( firstWS > 0 && doc.getToken(firstWS-1).type == Syntax.SingleLineComment ) + firstWS--; + if( firstWS < afterWS ) { + StringBuilder buf = new StringBuilder(); + for (int i=firstWS; i null); + if ( manifestResource ) { + const fileContent = await manifestResource.buffer(); + await this._analyzeManifest( JSON.parse(fileContent.toString()), info ); + } else { + log.verbose(`No manifest found for '${resource.name}', skipping analysis`); + } + } catch (err) { + log.error(`An error occurred while analyzing template app ${resource.name} (ignored): ${err.message}`); + log.verbose(err.stack); + } + + return info; + } + + /** + * Evaluates a manifest after it has been read and parsed + * and adds any newly found dependencies to the given info object. + * + * @param {object} manifest JSON with app descriptor structure + * @param {ModuleInfo} info ModuleInfo object that should be enriched + * @returns {ModuleInfo} ModuleInfo object that should be enriched + * @private + */ + async _analyzeManifest( manifest, info ) { + const promises = []; + const that = this; + const st = (manifest && manifest["sap.ui.generic.app"]) || {}; + function recursePage(page) { + if ( page.component && page.component.name ) { + const module = fromUI5LegacyName( page.component.name + ".Component" ); + log.verbose(`Template app: Add dependency to template component ${module}`); + info.addDependency(module); + promises.push( that._analyzeTemplateComponent(module, page, info) ); + } + recurse(page); + } + function recurse(ctx) { + if ( Array.isArray(ctx.pages) ) { + ctx.pages.forEach(recursePage); + } else if (typeof ctx.pages === "object") { + Object.values(ctx.pages).forEach(recursePage); + } + } + recurse(st); + + return Promise.all(promises); + } + + async _analyzeTemplateComponent(moduleName, pageConfig, appInfo) { + // console.log("analyzing template component %s", moduleName); + try { + const resource = await this._pool.findResource(moduleName); + const code = await resource.buffer(); + const ast = parseJS(code); + const defaultTemplateName = this._analyzeAST(moduleName, ast); + const templateName = (pageConfig.component && pageConfig.component.settings && + pageConfig.component.settings.templateName) || defaultTemplateName; + if ( templateName ) { + const templateModuleName = fromUI5LegacyName( templateName, ".view.xml" ); + log.verbose(`Template app: Add dependency to template view ${templateModuleName}`); + appInfo.addDependency(templateModuleName); + } + } catch (err) { + if (this._pool.getIgnoreMissingModules() && err.message.startsWith("resource not found in pool")) { + log.verbose(`Ignoring missing module as per ResourcePool configuration: ${err.message}`); + } else { + throw err; + } + } + } + + _analyzeAST(moduleName, ast) { + let templateName = ""; + if ( ast.body.length > 0 && (isMethodCall(ast.body[0].expression, CALL_SAP_UI_DEFINE) || + isMethodCall(ast.body[0].expression, CALL_DEFINE)) ) { + const defineCall = new SapUiDefine(ast.body[0].expression, moduleName); + const TA = defineCall.findImportName("sap/suite/ui/generic/template/lib/TemplateAssembler.js"); + // console.log("local name for TemplateAssembler: %s", TA); + if ( TA && defineCall.factory ) { + if (defineCall.factory.type === Syntax.ArrowFunctionExpression && + defineCall.factory.expression === true) { + if ( this._isTemplateClassDefinition(TA, defineCall.factory.body) ) { + templateName = + this._analyzeTemplateClassDefinition(defineCall.factory.body.arguments[2]) || templateName; + } + } else { + defineCall.factory.body.body.forEach( (stmt) => { + if ( stmt.type === Syntax.ReturnStatement && + this._isTemplateClassDefinition(TA, stmt.argument) + ) { + templateName = + this._analyzeTemplateClassDefinition(stmt.argument.arguments[2]) || templateName; + } + }); + } + } + } + return templateName; + } + + _isTemplateClassDefinition(TA, node) { + return isMethodCall(node, [TA, "getTemplateComponent"]) && + node.arguments.length > 2 && + node.arguments[2].type === "ObjectExpression"; + } + + _analyzeTemplateClassDefinition(clazz) { + return getStringValue(getValue(clazz, ["metadata", "properties", "templateName", "defaultValue"])); + } +} + +export default TemplateComponentAnalyzer; diff --git a/packages/builder/lib/lbt/analyzer/XMLCompositeAnalyzer.js b/packages/builder/lib/lbt/analyzer/XMLCompositeAnalyzer.js new file mode 100644 index 00000000000..45c76c9ef20 --- /dev/null +++ b/packages/builder/lib/lbt/analyzer/XMLCompositeAnalyzer.js @@ -0,0 +1,75 @@ + +import {Syntax} from "../utils/parseUtils.js"; +import SapUiDefine from "../calls/SapUiDefine.js"; +import {getValue, isMethodCall, getStringValue} from "../utils/ASTUtils.js"; +import {fromUI5LegacyName} from "../utils/ModuleName.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:analyzer:XMLCompositeAnalyzer"); + +const CALL_DEFINE = ["define"]; +const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"]; + +class XMLCompositeAnalyzer { + analyze(ast, moduleName, info) { + let fragmentName; + if ( ast.body.length > 0 && ast.body[0].type === Syntax.ExpressionStatement && + (isMethodCall(ast.body[0].expression, CALL_SAP_UI_DEFINE) || + isMethodCall(ast.body[0].expression, CALL_DEFINE)) ) { + const defineCall = new SapUiDefine(ast.body[0].expression, moduleName); + const XMLC = defineCall.findImportName("sap/ui/core/XMLComposite.js"); + // console.log("local name for XMLComposite: %s", XMLC); + if ( XMLC && defineCall.factory ) { + if (defineCall.factory.type === Syntax.ArrowFunctionExpression && + defineCall.factory.expression === true) { + fragmentName = this._checkForXMLCClassDefinition(XMLC, defineCall.factory.body); + } else { + defineCall.factory.body.body.forEach((stmt) => { + if (stmt.type === Syntax.VariableDeclaration) { + stmt.declarations.forEach((decl) => { + fragmentName = this._checkForXMLCClassDefinition(XMLC, decl.init) || fragmentName; + }); + } else if ( + stmt.type === Syntax.ReturnStatement && + ( stmt?.argument?.type === Syntax.CallExpression && stmt.argument.arguments?.length > 1 && + stmt.argument.arguments[1].type === Syntax.ObjectExpression)) { + fragmentName = + this._checkForXMLCClassDefinition(XMLC, stmt.argument) || fragmentName; + } else if (stmt.type === Syntax.ExpressionStatement && + stmt.expression.type === Syntax.AssignmentExpression) { + fragmentName = + this._checkForXMLCClassDefinition(XMLC, stmt.expression.right) || fragmentName; + } + }); + } + if (fragmentName) { + const fragmentModule = fromUI5LegacyName(fragmentName, ".control.xml"); + log.verbose(`Fragment control: Add dependency to template fragment ${fragmentModule}`); + info.addDependency(fragmentModule); + } + } + } + } + + _checkForXMLCClassDefinition(XMLC, stmt) { + let fragmentName; + if ( isMethodCall(stmt, [XMLC, "extend"]) ) { + // log.verbose(stmt); + const value = getStringValue(stmt.arguments[0]); + if ( stmt.arguments.length > 0 && value ) { + fragmentName = value; + } + if ( stmt.arguments.length > 1 && stmt.arguments[1].type === Syntax.ObjectExpression ) { + fragmentName = this._analyzeXMLCClassDefinition(stmt.arguments[1]) || fragmentName; + } + } + return fragmentName; + } + + _analyzeXMLCClassDefinition(clazz) { + // log.verbose(clazz); + return getStringValue(getValue(clazz, ["fragment"])); + } +} + + +export default XMLCompositeAnalyzer; diff --git a/packages/builder/lib/lbt/analyzer/XMLTemplateAnalyzer.js b/packages/builder/lib/lbt/analyzer/XMLTemplateAnalyzer.js new file mode 100644 index 00000000000..e6023e019f2 --- /dev/null +++ b/packages/builder/lib/lbt/analyzer/XMLTemplateAnalyzer.js @@ -0,0 +1,417 @@ + +import xml2js from "xml2js"; +import {fromUI5LegacyName, fromRequireJSName} from "../utils/ModuleName.js"; +import JSTokenizer from "../utils/JSTokenizer.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:analyzer:XMLTemplateAnalyzer"); + +// --------------------------------------------------------------------------------------------------------- + +/* + * TODOS + * - find better way to distinguish between aggregation tags and control tags + * (currently, existence in pool is used to recognize controls) + * - support alternative namespace URLs for libraries (as used by XSD files) + * - make set of view types configurable + * - plugin mechanism to support other special controls + * - move UI5 specific constants to UI5ClientConstants? + */ + +// --------------------------------------------------------------------------------------------------------- + +const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; +const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; +const TEMPLATING_NAMESPACE = "http://schemas.sap.com/sapui5/extension/sap.ui.core.template/1"; +const TEMPLATING_CONDITONAL_TAGS = /^(?:if|repeat)$/; + +const PATTERN_LIBRARY_NAMESPACES = /^([a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)$/; + +// component container +const COMPONENTCONTAINER_MODULE = "sap/ui/core/ComponentContainer.js"; +const COMPONENTCONTAINER_COMPONENTNAME_ATTRIBUTE = "name"; + +// fragment definition +const FRAGMENTDEFINITION_MODULE = "sap/ui/core/FragmentDefinition.js"; + +// fragment +const FRAGMENT_MODULE = "sap/ui/core/Fragment.js"; +const FRAGMENT_FRAGMENTNAME_ATTRIBUTE = "fragmentName"; +const FRAGMENT_TYPE_ATTRIBUTE = "type"; + +// different view types +const VIEW_MODULE = "sap/ui/core/mvc/View.js"; +const HTMLVIEW_MODULE = "sap/ui/core/mvc/HTMLView.js"; +const JSVIEW_MODULE = "sap/ui/core/mvc/JSView.js"; +const JSONVIEW_MODULE = "sap/ui/core/mvc/JSONView.js"; +const XMLVIEW_MODULE = "sap/ui/core/mvc/XMLView.js"; +const TEMPLATEVIEW_MODULE = "sap/ui/core/mvc/TemplateView.js"; +const ANYVIEW_VIEWNAME_ATTRIBUTE = "viewName"; +const XMLVIEW_CONTROLLERNAME_ATTRIBUTE = "controllerName"; +const XMLVIEW_RESBUNDLENAME_ATTRIBUTE = "resourceBundleName"; +const XMLVIEW_CORE_REQUIRE_ATTRIBUTE_NS = { + uri: "sap.ui.core", + local: "require" +}; +const VIEW_TYPE_ATTRIBUTE = "type"; + +/* + * Helper to simplify access to node attributes. + */ +function getAttribute(node, attr) { + return (node.$ && node.$[attr] && node.$[attr].value) || null; +} +function getAttributeNS(node, attrNS) { + const attr = Object.values(node.$ || []).find((n) => { + return n.uri === attrNS.uri && n.local === attrNS.local; + }); + return (attr && attr.value) || null; +} + +/** + * A dependency analyzer for XMLViews and XMLFragments. + * + * Parses the XML, collects controls and adds them as dependency to the ModuleInfo object. + * Additionally, some special dependencies are handled: + * + * + * In an XMLView, there usually exist 3 categories of element nodes: controls, aggregations + * of cardinality 'multiple' and non-UI5 nodes (e.g. XHTML or SVG). The third category usually + * can be identified by its namespace. To distinguish between the first and the second + * category, this analyzer uses a ResourcePool (provided by the caller and usually derived from the + * library classpath). When the qualified node name is contained in the pool, it is assumed to + * represent a control, otherwise it is ignored. + * + * In certain cases this might give wrong results, but loading the metadata for each control + * to implement the exactly same logic as used in the runtime XMLTemplateProcessor would be to + * expensive and require too much runtime. + * + * @author Frank Weigel + * @since 1.23.0 + * @private + */ +class XMLTemplateAnalyzer { + constructor(pool) { + this._pool = pool; + this._parser = new xml2js.Parser({ + explicitRoot: false, + explicitChildren: true, + preserveChildrenOrder: true, + xmlns: true + }); + this.busy = false; + } + + /** + * Add a dependency if it is new. + * + * @param {string} moduleName + * @param {boolean} conditional + */ + _addDependency(moduleName, conditional) { + // don't add references to 'self' + if ( this.info.name === moduleName ) { + return; + } + + // don't add properties with data binding syntax + if (moduleName.includes("{") || moduleName.includes("}")) { + return; + } + + this.info.addDependency(moduleName, conditional); + } + + /** + * Enrich the given ModuleInfo for an XMLView. + * + * @param {string} xml xml string to be analyzed + * @param {ModuleInfo} info ModuleInfo to enrich + * @returns {Promise} the created ModuleInfo + */ + analyzeView(xml, info) { + return this._analyze(xml, info, false); + } + + /** + * Enrich the given ModuleInfo for a fragment (XML). + * + * @param {string} xml xml string to be analyzed + * @param {ModuleInfo} info ModuleInfo to enrich + * @returns {Promise} the created ModuleInfo + */ + analyzeFragment(xml, info) { + return this._analyze(xml, info, true); + } + + _analyze(xml, info, isFragment) { + if ( this.busy ) { + // TODO delegate to fresh instances instead + throw new Error("XMLTemplateAnalyzer is unexpectedly busy"); + } + + this.info = info; + this.conditional = false; + this.templateTag = false; + this.promises = []; + this.busy = true; + + return new Promise( (resolve, reject) => { + this._parser.parseString(xml, (err, result) => { + // parse error + if ( err ) { + this.busy = false; + reject(new Error(`Error while parsing XML document ${info.name}: ${err.message}`)); + return; + } + + if ( !result ) { + // Handle empty xml views/fragments + reject(new Error("Invalid empty XML document: " + info.name)); + return; + } + + // console.log(result); + // clear(); + if ( isFragment ) { + // all fragments implicitly depend on the fragment class + this.info.addImplicitDependency(FRAGMENT_MODULE); + this._analyzeNode(result); + } else { + // views require a special handling of the root node + this._analyzeViewRootNode(result); + } + + Promise.all(this.promises).then( () => { + this.busy = false; + resolve(info); + }); + // console.log("Collected info for %s:", info.name, info); + }); + }); + } + + _analyzeViewRootNode(node) { + this.info.addImplicitDependency(XMLVIEW_MODULE); + + const controllerName = getAttribute(node, XMLVIEW_CONTROLLERNAME_ATTRIBUTE); + if ( controllerName ) { + this._addDependency( fromUI5LegacyName(controllerName, ".controller.js"), this.conditional ); + } + + const resourceBundleName = getAttribute(node, XMLVIEW_RESBUNDLENAME_ATTRIBUTE); + if ( resourceBundleName ) { + const resourceBundleModuleName = fromUI5LegacyName(resourceBundleName, ".properties"); + log.verbose(`Found dependency to resource bundle ${resourceBundleModuleName}`); + // TODO locale dependent dependencies: this._addDependency(resourceBundleModuleName); + this._addDependency( resourceBundleModuleName, this.conditional ); + } + + this._analyzeCoreRequire(node); + + this._analyzeChildren(node); + } + + _analyzeNode(node) { + const namespace = node.$ns.uri || ""; + const localName = node.$ns.local; + + const oldConditional = this.conditional; + const oldTemplateTag = this.templateTag; + + if ( namespace === TEMPLATING_NAMESPACE ) { + if ( TEMPLATING_CONDITONAL_TAGS.test(localName) ) { + this.conditional = true; + } + this.templateTag = true; + } else if ( namespace === XHTML_NAMESPACE || namespace === SVG_NAMESPACE ) { + + // ignore XHTML and SVG nodes + + } else if ( PATTERN_LIBRARY_NAMESPACES.test(namespace) ) { + // looks like a UI5 library or package name + const moduleName = fromUI5LegacyName( (namespace ? namespace + "." : "") + localName ); + + this._analyzeCoreRequire(node); + + // ignore FragmentDefinition (also skipped by runtime XMLTemplateProcessor) + if ( FRAGMENTDEFINITION_MODULE !== moduleName ) { + this.promises.push(this._analyzeModuleDependency(node, moduleName, this.conditional)); + } + } + + this._analyzeChildren(node); + + // restore conditional and templateTag state of the outer block + this.conditional = oldConditional; + this.templateTag = oldTemplateTag; + } + + _analyzeChildren(node) { + if ( Array.isArray(node.$$) ) { + node.$$.forEach( (child) => { + return this._analyzeNode( child); + }); + } + } + + _analyzeCoreRequire(node) { + const coreRequire = getAttributeNS(node, XMLVIEW_CORE_REQUIRE_ATTRIBUTE_NS); + let requireContext; + if ( coreRequire ) { + // expression binding syntax within coreRequire and a template parent node + // These expressions cannot be parsed using parseJS and if within a template tag + // represent an expression binding which needs to be evaluated before analysis + // e.g. "{= '{Handler: \'' + ${myActions > handlerModule} + '\'}'}" + if ((coreRequire.startsWith("{=") || coreRequire.startsWith("{:=")) && this.templateTag) { + log.verbose( + `Ignoring core:require: '${coreRequire}' on Node ${node.$ns.uri}:${node.$ns.local} contains ` + + `an expression binding and is within a 'template' Node` + ); + return; + } + + try { + requireContext = JSTokenizer.parseJS(coreRequire); + } catch (e) { + log.error( + `Ignoring core:require: '${coreRequire}' can't be parsed on Node ` + + `${node.$ns.uri}:${node.$ns.local}: ${e.message}` + ); + log.verbose(e.stack); + } + if ( requireContext ) { + Object.keys(requireContext).forEach((key) => { + const requireJsName = requireContext[key]; + if ( requireJsName && typeof requireJsName === "string" ) { + this._addDependency(fromRequireJSName(requireJsName), this.conditional); + } else { + log.error(`Ignoring core:require: '${key}' refers to invalid module name '${requireJsName}'`); + } + }); + } + } + } + + async _analyzeModuleDependency(node, moduleName, conditional) { + try { + await this._pool.findResource(moduleName); + + this._addDependency(moduleName, conditional); + + // handle special controls that reference other entities via name + // - (HTML|JS|JSON|XML)View reference another view by 'viewName' + // - ComponentContainer reference another component by 'componentName' + // - Fragment references a fragment by 'fragmentName' . 'type' + + if ( moduleName === COMPONENTCONTAINER_MODULE ) { + const componentName = getAttribute(node, COMPONENTCONTAINER_COMPONENTNAME_ATTRIBUTE); + if ( componentName ) { + const componentModuleName = + fromUI5LegacyName( componentName, "/Component.js" ); + this._addDependency(componentModuleName, conditional); + } + // TODO what about component.json? handle it transitively via Component.js? + } else if ( moduleName === FRAGMENT_MODULE ) { + const fragmentName = getAttribute(node, FRAGMENT_FRAGMENTNAME_ATTRIBUTE); + const type = getAttribute(node, FRAGMENT_TYPE_ATTRIBUTE); + if ( fragmentName && type ) { + const fragmentModuleName = + fromUI5LegacyName( fragmentName, this._getFragmentExtension(type) ); + // console.log("child fragment detected %s", fragmentModuleName); + this._addDependency(fragmentModuleName, conditional); + } + } else if ( moduleName === HTMLVIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + const childViewModuleName = fromUI5LegacyName( viewName, ".view.html" ); + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } else if ( moduleName === JSVIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + const childViewModuleName = fromUI5LegacyName( viewName, ".view.js" ); + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } else if ( moduleName === JSONVIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + const childViewModuleName = fromUI5LegacyName( viewName, ".view.json" ); + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } else if ( moduleName === XMLVIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + const childViewModuleName = fromUI5LegacyName( viewName, ".view.xml" ); + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } else if ( moduleName === TEMPLATEVIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + const childViewModuleName = fromUI5LegacyName( viewName, ".view.tmpl" ); + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } else if ( moduleName === VIEW_MODULE ) { + const viewName = getAttribute(node, ANYVIEW_VIEWNAME_ATTRIBUTE); + if ( viewName ) { + let childViewModuleName; + + if (viewName.startsWith("module:")) { + childViewModuleName = viewName.slice("module:".length) + ".js"; + } else { + const viewType = getAttribute(node, VIEW_TYPE_ATTRIBUTE); + + let viewTypeExtension; + + switch (viewType) { + case "JS": + viewTypeExtension = ".view.js"; + break; + case "JSON": + viewTypeExtension = ".view.json"; + break; + case "Template": + viewTypeExtension = ".view.tmpl"; + break; + case "XML": + viewTypeExtension = ".view.xml"; + break; + case "HTML": + viewTypeExtension = ".view.html"; + break; + default: + log.warn(`Unable to analyze sap.ui5/rootView: Unknown type '${viewType}'`); + } + + if (viewTypeExtension) { + childViewModuleName = fromUI5LegacyName(viewName, viewTypeExtension); + } + } + if (childViewModuleName) { + // console.log("child view detected %s", childViewModuleName); + this._addDependency(childViewModuleName, conditional); + } + } + } + } catch { + // ignore missing resources + // console.warn( "node not found %s", moduleName); + } + } + + _getFragmentExtension(type) { + return ".fragment." + type.toLowerCase(); + } +} + + +export default XMLTemplateAnalyzer; diff --git a/packages/builder/lib/lbt/analyzer/analyzeLibraryJS.js b/packages/builder/lib/lbt/analyzer/analyzeLibraryJS.js new file mode 100644 index 00000000000..16e7ca86205 --- /dev/null +++ b/packages/builder/lib/lbt/analyzer/analyzeLibraryJS.js @@ -0,0 +1,181 @@ + +import {parseJS, Syntax, VisitorKeys} from "../utils/parseUtils.js"; +import {getPropertyKey, isMethodCall, isIdentifier, getStringArray} from "../utils/ASTUtils.js"; +import SapUiDefineCall from "../calls/SapUiDefine.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:analyzer:LibraryJS"); + +const CALL__SAP_UI_GETCORE = ["sap", "ui", "getCore"]; +const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"]; +const CALL_AMD_DEFINE = ["define"]; +const AMD__LIB_INIT_RESOURCES = [ + // Dependency definition, init library method name + ["sap/ui/core/Core.js", "initLibrary"], + ["sap/ui/core/Lib.js", "init"] +]; + +/* + * Static Code Analyzer that extracts library information from the sap.ui.getCore().initLibrary() + * call in a library.js module. + */ +async function analyze(resource) { + const libInfo = { + noLibraryCSS: false, + types: [], + controls: [], + elements: [], + interfaces: [] + }; + + const code = await resource.getBuffer(); + const rootNode = parseJS(code); + let analyzedDefineCall = null; + visit( rootNode ); + + function visit(node) { + if ( node.type === Syntax.CallExpression ) { + // Define call would always be at the top of the tree, so it'd + // be the first significant thing that's been found. + analyzedDefineCall = analyzedDefineCall || analyzeSapUiDefineCalls(node); + + if (isInitLibraryCall(node)) { + node.arguments[0].properties.forEach( (prop) => { + if (prop.type === Syntax.SpreadElement) { + // SpreadElements are currently not supported + return; + } + + const key = getPropertyKey(prop); + const value = prop.value; + + if ( key === "noLibraryCSS" && + (value.type === Syntax.Literal && typeof value.value === "boolean") ) { + libInfo.noLibraryCSS = value.value; + } else if ( key === "types" && value.type == Syntax.ArrayExpression ) { + libInfo.types = getStringArray(value, true); + } else if ( key === "interfaces" && value.type == Syntax.ArrayExpression ) { + libInfo.interfaces = getStringArray(value, true); + } else if ( key === "controls" && value.type == Syntax.ArrayExpression ) { + libInfo.controls = getStringArray(value, true); + } else if ( key === "elements" && value.type == Syntax.ArrayExpression ) { + libInfo.elements = getStringArray(value, true); + } else if ( ["designtime", "dependencies", "extensions", "name", "version"].includes(key) ) { + // do nothing, for all other supported properties + } else if ( key === "apiVersion" && + (value.type === Syntax.Literal && typeof value.value === "number") ) { + // just a validation + } else { + log.error( + "Unexpected property '" + key + + "' or wrong type for '" + key + + "' for a library initialization call in '" + resource.getPath() + "'" + ); + } + }); + + return true; // abort, we're done + } + } + + for ( const key of VisitorKeys[node.type] ) { + const child = node[key]; + if ( Array.isArray(child) ) { + if ( child.some(visit) ) { + return true; + } + } else if ( child ) { + if ( visit(child) ) { + return true; + } + } + } + + return false; + } + + /** + * Finds and extracts dependencies from sap.ui.define call + * + * @param {Node} node + * @returns {SapUiDefineCall} + */ + function analyzeSapUiDefineCalls(node) { + // Analyze sap.ui.define calls + if ( [CALL_AMD_DEFINE, CALL_SAP_UI_DEFINE].some( + (defCall) => isMethodCall(node, defCall)) ) { + const defCall = new SapUiDefineCall(node, resource.getName()); + + return AMD__LIB_INIT_RESOURCES + // Resolve dependency variable names. + .map((dependency) => [defCall.findImportName(dependency[0]), dependency[1]]) + // Remove potential dependencies that are not actually present. + .filter((dependency) => dependency[0]); + } + + return null; + } + + /** + * Determines whether the node is an initLibrary call. + * + * @param {Node} node + * @returns {boolean} + */ + function isInitLibraryCall(node) { + // sap.ui.getCore() + if ( + node.type == Syntax.CallExpression && + node.callee.type === Syntax.MemberExpression && + isMethodCall(node.callee.object, CALL__SAP_UI_GETCORE) && + isIdentifier(node.callee.property, "initLibrary") && + node.arguments.length === 1 && + node.arguments[0].type === Syntax.ObjectExpression + ) { + return true; + } + + // Find a CallExpression that is initLibrary + // i.e. node.callee.object === "Core" && node.callee.property === "initLibrary" + if ( + node.type === Syntax.CallExpression && + node.callee.type === Syntax.MemberExpression && + analyzedDefineCall && + analyzedDefineCall.some((potentialDependency) => + isMethodCall(node, potentialDependency) + ) && + node.arguments.length === 1 && + node.arguments[0].type === Syntax.ObjectExpression + ) { + return true; + } + + return false; + } + + return libInfo; +} + +/** + * Creates a new analyzer and executes it on the given resource. + * + * If the resources exists and can be parsed as JavaScript and if an sap.ui.getCore().initLibrary() + * call is found in the code, then the following information will be set: + *
    + *
  • noLibraryCSS: false when the noLibraryCSS property had been set in the initLibrary info object)
  • + *
  • types: string array with the names of the types contained in the library
  • + *
  • controls: string array with the names of the controls defined in the library
  • + *
  • elements: string array with the names of the elements defined in the library
  • + *
  • interfaces: string array with the names of the interfaces defined in the library
  • + *
+ * + * Note: only the first initLibrary() call that is found with a DFS on the AST, will be evaluated. + * + * @param {@ui5/fs/Resource} resource library.js resource whose content should be analyzed + * @returns {Promise} A Promise on the extract info object + */ +export default function(resource) { + if ( resource == null ) { + return Promise.resolve({}); + } + return analyze(resource); +} diff --git a/packages/builder/lib/lbt/bundle/AutoSplitter.js b/packages/builder/lib/lbt/bundle/AutoSplitter.js new file mode 100644 index 00000000000..ff257f9eddc --- /dev/null +++ b/packages/builder/lib/lbt/bundle/AutoSplitter.js @@ -0,0 +1,353 @@ + +import {pd} from "pretty-data"; +import {toRequireJSName} from "../utils/ModuleName.js"; +import {SectionType} from "./BundleDefinition.js"; +import escapePropertiesFile from "../utils/escapePropertiesFile.js"; +import {makeStringLiteral} from "../utils/stringUtils.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:bundle:AutoSplitter"); + +const xmlHtmlPrePattern = /<(?:\w+:)?pre\b/; + +/** + * + * @author Frank Weigel + * @since 1.27.1 + * @private + */ +class AutoSplitter { + /** + * Used to split resources + * + * @param {ResourcePool} pool + * @param {Resolver} resolver + */ + constructor(pool, resolver) { + this.pool = pool; + this.resolver = resolver; + this.optimizeXMLViews = false; + } + + // TODO refactor JSMergedModuleBuilder(Ext) so that it can be used with a NullWriter to collect all resource sizes + // this would avoid the redundant compression code in this class + /** + * Runs the split operation + * + * @param {object} moduleDef + * @param {object} options + * @returns {Promise} + */ + async run(moduleDef, options) { + options = options || {}; + const numberOfParts = options.numberOfParts; + let totalSize = 0; + const moduleSizes = Object.create(null); + const depCacheSizes = []; + let depCacheLoaderSize = 0; + let bundleInfoLoaderSize = 0; + this.optimize = !!options.optimize; + + // ---- resolve module definition + const resolvedModule = await this.resolver.resolve(moduleDef); + // ---- calculate overall size of merged module + + if ( moduleDef.configuration ) { + totalSize += 1024; // just a rough estimate + } + + const promises = []; + + resolvedModule.sections.forEach( (section) => { + switch ( section.mode ) { + case SectionType.Provided: + // provided modules don't contribute to the final size + break; + case SectionType.Raw: + case SectionType.Preload: + section.modules.forEach( (module) => { + promises.push( + this._calcMinSize(module).then( (size) => { + totalSize += size; + moduleSizes[module] = size; + }) + ); + }); + break; + case SectionType.Require: + section.modules.forEach( (module) => { + totalSize += "sap.ui.requireSync('');".length + toRequireJSName(module).length; + }); + break; + case SectionType.DepCache: + depCacheLoaderSize = "sap.ui.loader.config({depCacheUI5:{}});".length; + totalSize += depCacheLoaderSize; + + section.modules.forEach( (module) => { + promises.push((async () => { + const resource = await this.pool.findResourceWithInfo(module); + const deps = resource.info.dependencies.filter( + (dep) => + !resource.info.isConditionalDependency(dep) && + !resource.info.isImplicitDependency(dep) + ); + if (deps.length > 0) { + const depSize = `"${module}": [${deps.map((dep) => `"${dep}"`).join(",")}],`.length; + totalSize += depSize; + + depCacheSizes.push({size: depSize, module}); + } + })()); + }); + break; + case SectionType.BundleInfo: + bundleInfoLoaderSize = "sap.ui.loader.config({bundlesUI5:{\n\n}});\n".length; + totalSize += bundleInfoLoaderSize; + totalSize += + `"${section.name}":[${section.modules.map(makeStringLiteral).join(",")}]`.length; + break; + default: + throw new Error(`Unknown section mode: ${section.mode}`); + } + }); + + await Promise.all(promises); + + const partSize = Math.floor(totalSize / numberOfParts); + log.verbose( + `Total size of modules ${totalSize} (chars), target size for each ` + + `of the ${numberOfParts} parts: ${partSize} (chars)`); + + // ---- create a separate module definition for each part + const splittedModules = []; + let moduleNameWithPart = moduleDef.name; + if ( !/__part__/.test(moduleNameWithPart) ) { + moduleNameWithPart = toRequireJSName(moduleNameWithPart) + "-__part__.js"; + } + // vars = Object.create(null); + + let part = 0; + totalSize = 0; + let currentModule = { + name: moduleNameWithPart.replace(/__part__/, part), + sections: [] + }; + // vars.put("part", Integer.toString(part)); + // currentModule.setName((ModuleName) ModuleNamePattern.resolvePlaceholders(moduleNameWithPart, vars)); + splittedModules.push(currentModule); + if ( moduleDef.configuration ) { + currentModule.configuration = moduleDef.configuration; + totalSize += 1024; // TODO calculate a real size? + } + + resolvedModule.sections.forEach( (section) => { + let currentSection; + let sequence; + let bundleInfoModuleStringLiterals; + switch ( section.mode ) { + case SectionType.Provided: + // 'provided' sections are no longer needed in a fully resolved module + break; + case SectionType.Raw: + // raw sections are always copied as a whole + currentSection = { + mode: SectionType.Raw, + filters: [] + }; + currentSection.declareRawModules = section.sectionDefinition.declareRawModules; + currentSection.sort = section.sectionDefinition.sort; + currentModule.sections.push( currentSection ); + section.modules.forEach( (module) => { + currentSection.filters.push(module); + totalSize += moduleSizes[module]; + }); + break; + case SectionType.Preload: + sequence = section.modules.slice(); + // simple version: just sort alphabetically + sequence.sort(); + + // NODE_TODO: sort by copyright: + // jsBuilder.beforeWriteFunctionPreloadSection((List) sequence); + + currentSection = { + mode: SectionType.Preload, + filters: [] + }; + currentSection.name = section.name; + currentModule.sections.push( currentSection ); + sequence.forEach( (module) => { + const moduleSize = moduleSizes[module]; + if ( part + 1 < numberOfParts && totalSize + moduleSize / 2 > partSize ) { + part++; + // start a new module + totalSize = 0; + currentModule = { + name: moduleNameWithPart.replace(/__part__/, part), + sections: [] + }; + // vars.put("part", Integer.toString(part)); + // currentModule.setName( + // (ModuleName) ModuleNamePattern.resolvePlaceholders(moduleNameWithPart, vars)); + splittedModules.push(currentModule); + currentSection = { + name: section.name, + mode: SectionType.Preload, + filters: [] + }; + currentModule.sections.push( currentSection ); + } + // add module to current section + currentSection.filters.push(module); + totalSize += moduleSize; + }); + break; + case SectionType.Require: + currentSection = { + mode: SectionType.Require, + filters: [] + }; + currentModule.sections.push( currentSection ); + section.modules.forEach( (module) => { + currentSection.filters.push( module ); + totalSize += 21 + toRequireJSName(module).length; + }); + break; + case SectionType.DepCache: + currentSection = { + mode: SectionType.DepCache, + filters: [] + }; + currentModule.sections.push( currentSection ); + totalSize += depCacheLoaderSize; + + depCacheSizes.forEach((depCache) => { + if ( part + 1 < numberOfParts && totalSize + depCache.size / 2 > partSize ) { + part++; + // start a new module + totalSize = depCacheLoaderSize; + currentSection = { + mode: SectionType.DepCache, + filters: [] + }; + currentModule = { + name: moduleNameWithPart.replace(/__part__/, part), + sections: [currentSection] + }; + splittedModules.push(currentModule); + } + + if (!currentSection.filters.includes(depCache.module)) { + currentSection.filters.push(depCache.module); + totalSize += depCache.size; + } + }); + break; + case SectionType.BundleInfo: + + // Create new part if size is already exceeded + if (part + 1 < numberOfParts && totalSize > partSize) { + part++; + currentModule = { + name: moduleNameWithPart.replace(/__part__/, part), + sections: [] + }; + splittedModules.push(currentModule); + } + + // bundleInfo sections are always copied as a whole + currentSection = { + mode: SectionType.BundleInfo, + name: section.name, + filters: [] + }; + currentModule.sections.push(currentSection); + + bundleInfoModuleStringLiterals = []; + section.modules.forEach((module) => { + currentSection.filters.push(module); + bundleInfoModuleStringLiterals.push(makeStringLiteral(module)); + }); + + totalSize += bundleInfoLoaderSize; + totalSize += + `"${section.name}":[${bundleInfoModuleStringLiterals.join(",")}]`.length; + + break; + default: + throw new Error(`Unknown section mode: ${section.mode}`); + } + }); + + log.verbose(`Splitted modules: ${splittedModules}`); + return splittedModules; + } + + async _calcMinSize(module) { + const resource = await this.pool.findResourceWithInfo(module); + if ( resource != null ) { + if ( resource.info && resource.info.compressedSize && + resource.info.compressedSize !== resource.info.size ) { + return resource.info.compressedSize; + } + + if ( /\.js$/.test(module) ) { + // No optimize / minify step here as the input should be + // either already optimized or not, based on the bundle options + const fileContent = await resource.buffer(); + return fileContent.length; + } else if ( /\.properties$/.test(module) ) { + /* NODE-TODO minimize *.properties + Properties props = new Properties(); + props.load(in); + in.close(); + Writer out = new StringWriter(); + props.store(out, ""); + return out.toString().length(); + */ + + // Since AutoSplitter is also used when splitting non-project resources (e.g. dependencies) + // *.properties files should be escaped if encoding option is specified + const fileContent = await escapePropertiesFile(resource); + + return fileContent.length; + } else if ( this.optimizeXMLViews && /\.view.xml$/.test(module) ) { + // needs to be activated when it gets activated in JSMergedModuleBuilderExt + let fileContent = await resource.buffer(); + if ( this.optimize ) { + // For XML we use the pretty data + // Do not minify if XML(View) contains an <*:pre> tag because whitespace of + // HTML
 should be preserved (should only happen rarely)
+					if (!xmlHtmlPrePattern.test(fileContent.toString())) {
+						fileContent = pd.xmlmin(fileContent.toString(), false);
+					}
+				}
+				return fileContent.length;
+			}
+
+			// if there is no precompiled information about the resource, just determine its length
+			if ( !resource.info && /\.(js|json|xml)$/.test(module) ) {
+				const fileContent = await resource.buffer();
+				return fileContent.length;
+			}
+		}
+
+		return resource && resource.info && resource.info.size ? resource.info.size : 0;
+	}
+
+	/* NODE-TODO debug resources
+	private URL findResource(ModuleName name) {
+		URL result = null;
+		if ( debugMode ) {
+			ModuleName debugName = ModuleName.getDebugName(name);
+			if ( debugName != null ) {
+				result = pool.findResource(debugName);
+			}
+		}
+		if ( result == null ) {
+			result = pool.findResource(name);
+		}
+		return result;
+	} */
+}
+
+export default AutoSplitter;
diff --git a/packages/builder/lib/lbt/bundle/Builder.js b/packages/builder/lib/lbt/bundle/Builder.js
new file mode 100644
index 00000000000..399bc330753
--- /dev/null
+++ b/packages/builder/lib/lbt/bundle/Builder.js
@@ -0,0 +1,910 @@
+/* eslint quotes: ["error", "double", { "allowTemplateLiterals": true }] */
+// for consistency of write calls, we generally allow template literals
+
+import path from "node:path";
+import {pd} from "pretty-data";
+import {parseJS, Syntax} from "../utils/parseUtils.js";
+import {encode as encodeMappings, decode as decodeMappings} from "@jridgewell/sourcemap-codec";
+import {isMethodCall} from "../utils/ASTUtils.js";
+import {toUI5LegacyName, toRequireJSName} from "../utils/ModuleName.js";
+import {
+	MODULE__UI5LOADER, MODULE__UI5LOADER_AUTOCONFIG,
+	MODULE__JQUERY_SAP_GLOBAL, MODULE__SAP_UI_CORE_CORE} from "../UI5ClientConstants.js";
+import escapePropertiesFile from "../utils/escapePropertiesFile.js";
+import {makeStringLiteral, removeHashbang} from "../utils/stringUtils.js";
+import BundleResolver from "./Resolver.js";
+import BundleSplitter from "./AutoSplitter.js";
+import {SectionType} from "./BundleDefinition.js";
+import BundleWriter from "./BundleWriter.js";
+import {getLogger} from "@ui5/logger";
+import semver from "semver";
+const log = getLogger("lbt:bundle:Builder");
+
+const sourceMappingUrlPattern = /\/\/# sourceMappingURL=(\S+)\s*$/;
+const httpPattern = /^https?:\/\//i;
+const xmlHtmlPrePattern = /<(?:\w+:)?pre\b/;
+
+function isEmptyBundle(resolvedBundle) {
+	return resolvedBundle.sections.every((section) => section.modules.length === 0);
+}
+
+class BundleBuilder {
+	constructor(pool, targetUi5CoreVersion, allowStringBundling) {
+		this.pool = pool;
+		this.resolver = new BundleResolver(pool);
+		this.splitter = new BundleSplitter(pool, this.resolver);
+		this.targetUi5CoreVersion = targetUi5CoreVersion;
+		this.targetUi5CoreVersionMajor = undefined;
+		this.allowStringBundling = allowStringBundling;
+	}
+
+	getEffectiveUi5MajorVersion() {
+		if (this.targetUi5CoreVersionMajor !== undefined) {
+			return this.targetUi5CoreVersionMajor;
+		}
+
+		const parsedVersion = semver.parse(this.targetUi5CoreVersion);
+		if (parsedVersion) {
+			this.targetUi5CoreVersionMajor = parsedVersion.major;
+
+			// legacy-free versions include changes of the upcoming major version
+			// so we should treat them the same as the next major version
+			if (
+				parsedVersion.prerelease.includes("legacy-free") ||
+				parsedVersion.prerelease.includes("legacy-free-SNAPSHOT") // Maven snapshot version
+			) {
+				this.targetUi5CoreVersionMajor += 1;
+			}
+		} else {
+			// Assume legacy version if unable to determine the version
+			this.targetUi5CoreVersionMajor = null;
+		}
+		return this.targetUi5CoreVersionMajor;
+	}
+
+	generateAfterPreloads(section) {
+		let str = `}`;
+		if ( section.name ) {
+			str += `,"${section.name}"`;
+		}
+		str += `);\n`;
+		return str;
+	}
+
+	generateRequire(modules) {
+		const requireCallback = this.determineRequireCallback(modules) ?? "";
+		return `sap.ui.require([${
+			modules.map(($) => `"${toRequireJSName($)}"`).join(",\n")
+		}]${requireCallback});\n`;
+	}
+
+	determineRequireCallback(modules) {
+		if (this.getEffectiveUi5MajorVersion() >= 2) {
+			// Starting with UI5 2.0.0, method Core.boot does not exist anymore
+			return;
+		}
+		const coreModuleIndex = modules.indexOf(MODULE__SAP_UI_CORE_CORE);
+		if (coreModuleIndex === -1) {
+			return;
+		}
+		return `, (${
+			modules.map((m, i) => i === coreModuleIndex ? `Core` : `_m${i}`)
+		}) => Core.boot?.()`;
+	}
+
+	generateRequireSync(moduleName) {
+		return `sap.ui.requireSync("${toRequireJSName(moduleName)}");\n`;
+	}
+
+	executesLoaderOrCore(resolvedModule) {
+		return resolvedModule.executes(MODULE__UI5LOADER) ||
+			resolvedModule.executes(MODULE__UI5LOADER_AUTOCONFIG) ||
+			resolvedModule.executes(MODULE__JQUERY_SAP_GLOBAL) ||
+			resolvedModule.executes(MODULE__SAP_UI_CORE_CORE);
+	}
+
+	async createBundle(module, options) {
+		if ( options.numberOfParts > 1 ) {
+			const bundleInfos = [];
+			const submodules = await this.splitter.run( module, options );
+			for ( const submodule of submodules ) {
+				bundleInfos.push( await this._createBundle(submodule, options) );
+			}
+			return bundleInfos;
+		} else {
+			return this._createBundle(module, options);
+		}
+	}
+
+	async _createBundle(module, options) {
+		const resolvedModule = await this.resolver.resolve(module);
+		if ( options.skipIfEmpty && isEmptyBundle(resolvedModule) ) {
+			log.verbose("  Skipping empty bundle " + module.name);
+			return undefined;
+		}
+		log.verbose(`  Create '${resolvedModule.name}'`);
+
+		this.options = options || {};
+		this.optimize = !!this.options.optimize;
+		if (this.options.sourceMap === undefined) {
+			this.options.sourceMap = true;
+		}
+
+		// when decorateBootstrapModule is false,
+		// we don't write the optimized flag and don't write the try catch wrapper
+		this.shouldDecorate = this.options.decorateBootstrapModule &&
+				this.executesLoaderOrCore(resolvedModule);
+		// TODO is the following condition ok or should the availability of jquery.sap.global.js be configurable?
+		this.jqglobalAvailable = !resolvedModule.containsGlobal;
+		this.openModule(resolvedModule.name);
+
+		this._sourceMap = {
+			version: 3,
+			file: path.posix.basename(resolvedModule.name),
+			sections: [],
+		};
+		this._bundleName = resolvedModule.name;
+		this._inlineContentCounter = 0;
+
+		let bundleInfos = [];
+		// create all sections in sequence
+		for ( const section of resolvedModule.sections ) {
+			log.verbose(`    Adding section${section.name ? " '" + section.name + "'" : ""} of type ${section.mode}`);
+			if ( section.mode === SectionType.BundleInfo ) {
+				bundleInfos.push(section);
+			} else {
+				if ( bundleInfos.length > 0 ) {
+					await this.writeBundleInfos(bundleInfos);
+					bundleInfos = [];
+				}
+				await this.addSection(section);
+			}
+		}
+		if ( bundleInfos.length > 0 ) {
+			await this.writeBundleInfos(bundleInfos);
+			bundleInfos = [];
+		}
+
+		this.closeModule(resolvedModule);
+
+		const bundleInfo = await resolvedModule.createModuleInfo(this.pool, this.allowStringBundling);
+		bundleInfo.size = this.outW.length;
+
+		return {
+			name: module.name,
+			content: this.outW.toString(),
+			sourceMap: this.options.sourceMap ? JSON.stringify(this._sourceMap) : null,
+			bundleInfo: bundleInfo
+		};
+	}
+
+	openModule(module) {
+		this.outW = new BundleWriter();
+		this.missingRawDeclarations = [];
+
+		this.outW.writeln("//@ui5-bundle " + module);
+		if ( this.shouldDecorate ) {
+			this.outW.writeln(`window["sap-ui-optimized"] = true;`);
+			if ( this.options.addTryCatchRestartWrapper ) {
+				this.outW.writeln(`try {`);
+			}
+		}
+	}
+
+	writeWithSourceMap(content) {
+		const transientSourceName =
+			`${path.posix.basename(this._bundleName)}?bundle-code-${this._inlineContentCounter++}`;
+		const sourceMap = createTransientSourceMap({
+			moduleName: transientSourceName,
+			moduleContent: content,
+			includeContent: true
+		});
+		this.addSourceMap(this._bundleName, sourceMap);
+		this.outW.write(content);
+		this.outW.ensureNewLine();
+	}
+
+	closeModule(resolvedModule) {
+		if ( resolvedModule.containsCoreSync ) {
+			if ( this.getEffectiveUi5MajorVersion() >= 2 ) {
+				throw new Error("Requiring sap/ui/core/Core synchronously is not supported as of UI5 Version 2");
+			}
+			this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
+			this.writeWithSourceMap(
+				`// as this module contains the Core, we ensure that the Core has been booted\n` +
+				`sap.ui.getCore?.().boot?.();`);
+		}
+		if ( this.shouldDecorate && this.options.addTryCatchRestartWrapper ) {
+			this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
+			this.writeWithSourceMap(
+				`} catch(oError) {\n` +
+				`if (oError.name != "Restart") { throw oError; }\n` +
+				`}`);
+		}
+		if (this.options.sourceMap) {
+			this.outW.writeln(`//# sourceMappingURL=${path.posix.basename(resolvedModule.name)}.map`);
+		}
+	}
+
+	async addSection(section) {
+		this.ensureRawDeclarations();
+
+		switch (section.mode) {
+		case SectionType.Provided:
+			// do nothing
+			return undefined; // nothing to wait for
+		case SectionType.Raw:
+			return await this.writeRaw(section);
+		case SectionType.Preload:
+			return await this.writePreloadFunction(section);
+		case SectionType.BundleInfo:
+			return await this.writeBundleInfos([section]);
+		case SectionType.Require:
+			return await this.writeRequires(section);
+		case SectionType.DepCache:
+			return await this.writeDepCache(section);
+		default:
+			throw new Error("unknown section mode " + section.mode);
+		}
+	}
+
+	ensureRawDeclarations() {
+		if ( this.missingRawDeclarations.length && this.jqglobalAvailable ) {
+			this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
+			/* NODE-TODO, moduleName is not defined
+				It should contain the name of the module which is currently build (1st parameter of _createBundle).
+				But when the ui5loader is present, declareRawModules should be forced to false anyhow.
+			this.outW.writeln("jQuery.sap.declare('", toUI5LegacyName(moduleName), "');");
+			*/
+			this.missingRawDeclarations.forEach( (module) => {
+				// 2nd parameter set to 'false': do not create namespaces - they nevertheless would come too late
+				this.outW.writeln(`jQuery.sap.declare('${toUI5LegacyName(module)}', false);`);
+			});
+			this.missingRawDeclarations = [];
+		}
+	}
+
+	// TODO check that there are only JS modules contained
+	async writeRaw(section) {
+		// write all modules in sequence
+		for ( const moduleName of section.modules ) {
+			const resource = await this.pool.findResourceWithInfo(moduleName);
+			if ( resource != null ) {
+				this.outW.startSegment(moduleName);
+				this.outW.ensureNewLine();
+				this.outW.writeln("//@ui5-bundle-raw-include " + moduleName);
+				await this.writeRawModule(moduleName, resource);
+				const compressedSize = this.outW.endSegment();
+				log.verbose(`    ${moduleName} (${resource.info != null ? resource.info.size : -1},${compressedSize})`);
+				if ( section.declareRawModules ) {
+					this.missingRawDeclarations.push(moduleName);
+				}
+				if ( moduleName === MODULE__JQUERY_SAP_GLOBAL ) {
+					this.jqglobalAvailable = true;
+				}
+			} else {
+				log.error(`    Could not find module ${moduleName}`);
+			}
+		}
+	}
+
+	async writeRawModule(moduleName, resource) {
+		this.outW.ensureNewLine();
+		let moduleContent = (await resource.buffer()).toString();
+		moduleContent = removeHashbang(moduleContent);
+		if (this.options.sourceMap) {
+			let moduleSourceMap;
+			({moduleContent, moduleSourceMap} =
+				await this.getSourceMapForModule({
+					moduleName,
+					moduleContent,
+					resourcePath: resource.getPath()
+				}));
+			this.addSourceMap(moduleName, moduleSourceMap);
+		}
+		this.outW.write(moduleContent);
+		this.outW.ensureNewLine();
+	}
+
+	async writePreloadFunction(section) {
+		const outW = this.outW;
+
+		outW.ensureNewLine();
+
+		const sequence = section.modules.slice();
+
+		this.beforeWriteFunctionPreloadSection(sequence);
+
+		await this.rewriteAMDModules(sequence);
+		if ( sequence.length > 0 ) {
+			this.writeWithSourceMap(`sap.ui.require.preload({\n`);
+			let i = 0;
+			for ( const module of sequence ) {
+				const resource = await this.pool.findResourceWithInfo(module);
+				if ( resource != null ) {
+					if ( i>0 ) {
+						outW.writeln(",");
+					}
+					outW.write(`\t"${module.toString()}":`);
+					outW.startSegment(module);
+					await this.writePreloadModule(module, resource.info, resource);
+					const compressedSize = outW.endSegment();
+					log.verbose(`    ${module} (${resource.info != null ? resource.info.size : -1},${compressedSize})`);
+					i++;
+				} else {
+					log.error(`    Could not find module ${module}`);
+				}
+			}
+
+			if ( i > 0 ) {
+				outW.writeln();
+			}
+			outW.write(this.generateAfterPreloads(section));
+		}
+	}
+
+	beforeWriteFunctionPreloadSection(sequence) {
+		// simple version: just sort alphabetically
+		sequence.sort();
+	}
+
+	addSourceMap(moduleName, map) {
+		if (!map) {
+			throw new Error("No source map provided");
+		}
+
+		// Reminder on the structure of line-segments in the map:
+		// [generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex]
+		if (map.mappings.startsWith(";")) {
+			// If first line is not already mapped (typical for comments or parentheses), add a mapping to
+			// make sure that dev-tools (especially Chrome's) don't choose the end of the preceding module
+			// when the user tries to set a breakpoint from the bundle file
+			map.mappings = "AAAA" + map.mappings;
+		} else if (this.outW.columnOffset === 0 && !map.mappings.startsWith("A")) {
+			// If first column of the first line is not already mapped, add a mapping for the same reason as above.
+			// This is typical for transpiled code, where there is a bunch of generated code at the beginning that
+			// can't be mapped to the original source
+			if (map.mappings) {
+				map.mappings = "AAAA," + map.mappings;
+			} else {
+				// If there are no existing mappings (e.g. if the file is empty or contains only comments),
+				// do not specify any mapping. Especially Safari would otherwise reject the source map due to
+				// "Invalid Mappings"
+				map.mappings = "";
+			}
+		}
+
+		map.sourceRoot = path.posix.relative(
+			path.posix.dirname(this._bundleName), path.posix.dirname(moduleName));
+
+		this._sourceMap.sections.push({
+			offset: {
+				line: this.outW.lineOffset,
+				column: this.outW.columnOffset
+			},
+			map
+		});
+	}
+
+	async rewriteAMDModules(sequence) {
+		const outW = this.outW;
+
+		const remaining = [];
+		for ( const moduleName of sequence ) {
+			if ( /\.js$/.test(moduleName) ) {
+				const resource = await this.pool.findResourceWithInfo(moduleName);
+
+				if (resource.info?.requiresTopLevelScope && !this.allowStringBundling) {
+					this.logStringBundlingError(moduleName);
+					continue;
+				}
+
+				let moduleContent = (await resource.buffer()).toString();
+				moduleContent = removeHashbang(moduleContent);
+				let moduleSourceMap;
+				if (this.options.sourceMap) {
+					({moduleContent, moduleSourceMap} =
+						await this.getSourceMapForModule({
+							moduleName,
+							moduleContent,
+							resourcePath: resource.getPath()
+						}));
+				}
+
+				const rewriteRes = await rewriteDefine({
+					moduleName, moduleContent, moduleSourceMap
+				});
+				if (rewriteRes) {
+					const {moduleContent, moduleSourceMap} = rewriteRes;
+					outW.startSegment(moduleName);
+					outW.ensureNewLine();
+					if (moduleSourceMap) {
+						this.addSourceMap(moduleName, moduleSourceMap);
+					}
+					outW.write(moduleContent);
+					outW.ensureNewLine();
+					const compressedSize = outW.endSegment();
+					log.verbose(
+						`    ${moduleName} (${resource.info != null ? resource.info.size : -1},${compressedSize})`);
+				} else {
+					// keep unprocessed modules
+					remaining.push(moduleName);
+				}
+			} else {
+				// keep unprocessed modules
+				remaining.push(moduleName);
+			}
+		}
+
+		Array.prototype.splice.apply(sequence, [0, sequence.length].concat(remaining));
+	}
+
+	/**
+	 *
+	 * @param {string} moduleName module name
+	 * @param {ModuleInfo} info
+	 * @param {@ui5/fs/Resource} resource
+	 * @returns {Promise}
+	 */
+	async writePreloadModule(moduleName, info, resource) {
+		const outW = this.outW;
+
+		if ( /\.js$/.test(moduleName) && (info == null || !info.requiresTopLevelScope) ) {
+			outW.writeln(`function(){`);
+			// The module should be written to a new line in order for dev-tools to map breakpoints to it
+			outW.ensureNewLine();
+			let moduleContent = (await resource.buffer()).toString();
+			moduleContent = removeHashbang(moduleContent);
+			if (this.options.sourceMap) {
+				let moduleSourceMap;
+				({moduleContent, moduleSourceMap} =
+					await this.getSourceMapForModule({
+						moduleName,
+						moduleContent,
+						resourcePath: resource.getPath()
+					}));
+
+				this.addSourceMap(moduleName, moduleSourceMap);
+			}
+			outW.write(moduleContent);
+			this.exportGlobalNames(info);
+			outW.ensureNewLine();
+			outW.write(`}`);
+		} else if ( /\.js$/.test(moduleName) /* implicitly: && info != null && info.requiresTopLevelScope */ ) {
+			log.warn(
+				`Module ${moduleName} requires top level scope and can only be embedded as a string (requires 'eval')`);
+			let moduleContent = (await resource.buffer()).toString();
+			moduleContent = removeHashbang(moduleContent);
+			if (this.options.sourceMap) {
+				// We are actually not interested in the source map this module might contain,
+				// but we should make sure to remove any "sourceMappingURL" from the module content before
+				// writing it to the bundle. Otherwise browser dev-tools might create unnecessary
+				// (and likely incorrect) requests for any referenced .map files
+				({moduleContent} =
+					await this.getSourceMapForModule({
+						moduleName,
+						moduleContent,
+						resourcePath: resource.getPath()
+					}));
+			}
+			outW.write( makeStringLiteral(moduleContent) );
+		} else if ( /\.html$/.test(moduleName) ) {
+			const fileContent = (await resource.buffer()).toString();
+			outW.write( makeStringLiteral( fileContent ) );
+		} else if ( /\.json$/.test(moduleName) ) {
+			let fileContent = (await resource.buffer()).toString();
+			if ( this.optimize ) {
+				try {
+					fileContent = JSON.stringify( JSON.parse( fileContent) );
+				} catch (e) {
+					log.verbose(`Failed to parse JSON file ${moduleName}. Ignoring error, skipping compression.`);
+					log.verbose(e);
+				}
+			}
+			outW.write(makeStringLiteral(fileContent));
+		} else if ( /\.xml$/.test(moduleName) ) {
+			let fileContent = (await resource.buffer()).toString();
+			if ( this.optimize ) {
+				// For XML we use the pretty data
+				// Do not minify if XML(View) contains an <*:pre> tag,
+				// because whitespace of HTML 
 should be preserved (should only happen rarely)
+				if (!xmlHtmlPrePattern.test(fileContent)) {
+					fileContent = pd.xmlmin(fileContent, false);
+				}
+			}
+			outW.write( makeStringLiteral( fileContent ) );
+		} else if ( /\.properties$/.test(moduleName) ) {
+			// Since the Builder is also used when building non-project resources (e.g. dependencies)
+			// *.properties files should be escaped if encoding option is specified
+			const fileContent = await escapePropertiesFile(resource);
+
+			outW.write( makeStringLiteral( fileContent ) );
+		} else {
+			log.error("Don't know how to embed module " + moduleName); // TODO throw?
+		}
+
+		return true;
+	}
+
+	/**
+	 * Create exports for globals
+	 *
+	 * @param {ModuleInfo} info
+	 */
+	exportGlobalNames(info) {
+		if ( !info || !info.exposedGlobals || !info.exposedGlobals.length ) {
+			return;
+		}
+		this.outW.ensureNewLine();
+		info.exposedGlobals.forEach( (globalName) => {
+			// Note: globalName can be assumed to be a valid identifier as it is used as variable name anyhow
+			this.writeWithSourceMap(`this.${globalName}=${globalName};\n`);
+		});
+	}
+
+	logStringBundlingError(moduleName) {
+		log.error(
+			"Module " + moduleName + " requires top level scope and can only be embedded as a string " +
+			"(requires 'eval'), which is not supported with specVersion 4.0 and higher. " +
+			"For more information, see the UI5 CLI documentation " +
+			"https://ui5.github.io/cli/stable/pages/Builder/#javascript-files-requiring-top-level-scope");
+	}
+
+	async checkForStringBundling(moduleName) {
+		if (!this.allowStringBundling && /\.js$/.test(moduleName)) {
+			const resource = await this.pool.findResourceWithInfo(moduleName);
+			if (resource.info?.requiresTopLevelScope) {
+				this.logStringBundlingError(moduleName);
+				return null;
+			}
+		}
+		return moduleName;
+	}
+
+	async writeBundleInfos(sections) {
+		this.outW.ensureNewLine();
+
+		let bundleInfoStr = "";
+		if ( sections.length > 0 ) {
+			bundleInfoStr = "sap.ui.loader.config({bundlesUI5:{\n";
+			let initial = true;
+			for (let idx = 0; idx < sections.length; idx++) {
+				const section = sections[idx];
+
+				// Remove modules requiring string bundling
+				let modules = await Promise.all(section.modules.map(this.checkForStringBundling.bind(this)));
+				modules = modules.filter(($) => $) || [];
+
+				if (!initial) {
+					bundleInfoStr += ",\n";
+				} else {
+					initial = false;
+				}
+
+				if (!section.name) {
+					throw new Error(`A 'bundleInfo' section is missing the mandatory 'name' property.` );
+				}
+				if (!path.extname(section.name)) {
+					log.warn(`BundleInfo section name '${section.name}' is missing a file extension. ` +
+						`The info might not work as expected. ` +
+						`The name must match the bundle filename (incl. extension such as '.js')`);
+				}
+				bundleInfoStr += `"${section.name}":[${modules.map(makeStringLiteral).join(",")}]`;
+			}
+			bundleInfoStr += "\n}});\n";
+
+			this.writeWithSourceMap(bundleInfoStr);
+		}
+	}
+
+	writeRequires(section) {
+		if (section.modules.length === 0) {
+			return;
+		}
+		this.outW.ensureNewLine();
+		if (section.async === false) {
+			section.modules.forEach( (module) => {
+				this.writeWithSourceMap(this.generateRequireSync(module));
+			});
+		} else {
+			this.writeWithSourceMap(this.generateRequire(section.modules));
+		}
+	}
+
+	// When AutoSplit is enabled for depCache, we need to ensure that modules
+	// are not duplicated across files. This might happen due to the filters provided.
+	// So, certain modules that are included in depCache could be dependencies of another
+	// module in the next file. This will also duplicate its dependency definition if we do not filter.
+	#depCacheSet = new Set();
+	async writeDepCache(section) {
+		let hasDepCache = false;
+
+		const sequence = section.modules.slice().sort();
+
+		if (sequence.length > 0) {
+			for (const module of sequence) {
+				if (this.#depCacheSet.has(module)) {
+					continue;
+				}
+
+				this.#depCacheSet.add(module);
+				let resource = null;
+				try {
+					resource = await this.pool.findResourceWithInfo(module);
+				} catch {
+					log.error(`    couldn't find ${module}`);
+				}
+
+				if (resource != null) {
+					const deps = resource.info.dependencies.filter(
+						(dep) =>
+							!resource.info.isConditionalDependency(dep) &&
+							!resource.info.isImplicitDependency(dep)
+					);
+					if (deps.length > 0) {
+						if (!hasDepCache) {
+							hasDepCache = true;
+							this.outW.ensureNewLine();
+							this.outW.writeln(`sap.ui.loader.config({depCacheUI5:{`);
+						}
+
+						this.outW.writeln(
+							`"${module}": [${deps.map((dep) => `"${dep}"`).join(",")}],`
+						);
+					} else {
+						log.verbose(`    skipped ${module}, no dependencies`);
+					}
+				}
+			}
+
+			if (hasDepCache) {
+				this.outW.writeln(`}});`);
+			}
+		}
+	}
+
+	async getSourceMapForModule({moduleName, moduleContent, resourcePath}) {
+		let moduleSourceMap = null;
+		let newModuleContent = moduleContent;
+
+		const sourceMapUrlMatch = moduleContent.match(sourceMappingUrlPattern);
+		if (sourceMapUrlMatch) {
+			const sourceMapUrl = sourceMapUrlMatch[1];
+			log.silly(`Found source map reference in content of module ${moduleName}: ${sourceMapUrl}`);
+
+			// Strip sourceMappingURL from module code to be bundled
+			// It has no effect and might be cause for confusion
+			newModuleContent = moduleContent.replace(sourceMappingUrlPattern, "");
+
+			if (sourceMapUrl) {
+				if (sourceMapUrl.startsWith("data:")) {
+					// Data-URI indicates an inline source map
+					const expectedTypeAndEncoding = "data:application/json;charset=utf-8;base64,";
+					if (sourceMapUrl.startsWith(expectedTypeAndEncoding)) {
+						const base64Content = sourceMapUrl.slice(expectedTypeAndEncoding.length);
+						moduleSourceMap = Buffer.from(base64Content, "base64").toString();
+					} else {
+						log.warn(
+							`Source map reference in module ${moduleName} is a data URI but has an unexpected` +
+							`encoding: ${sourceMapUrl}. Expected it to start with ` +
+							`"data:application/json;charset=utf-8;base64,"`);
+					}
+				} else if (httpPattern.test(sourceMapUrl)) {
+					log.warn(`Source map reference in module ${moduleName} is an absolute URL. ` +
+						`Currently, only relative URLs are supported.`);
+				} else if (path.posix.isAbsolute(sourceMapUrl)) {
+					log.warn(`Source map reference in module ${moduleName} is an absolute path. ` +
+						`Currently, only relative paths are supported.`);
+				} else {
+					const sourceMapPath = path.posix.join(path.posix.dirname(moduleName), sourceMapUrl);
+
+					try {
+						const sourceMapResource = await this.pool.findResource(sourceMapPath);
+						moduleSourceMap = (await sourceMapResource.buffer()).toString();
+					} catch (e) {
+						// No input source map
+						log.warn(`Unable to read source map for module ${moduleName}: ${e.message}`);
+					}
+				}
+			}
+		} else {
+			const sourceMapFileCandidate = resourcePath.slice("/resources/".length) + ".map";
+			log.silly(`Could not find a sourceMappingURL reference in content of module ${moduleName}. ` +
+				`Attempting to find a source map resource based on the module's path: ${sourceMapFileCandidate}`);
+			try {
+				const sourceMapResource = await this.pool.findResource(sourceMapFileCandidate);
+				moduleSourceMap = (await sourceMapResource.buffer()).toString();
+			} catch (e) {
+				// No input source map
+				log.silly(`Could not find a source map for module ${moduleName}: ${e.message}`);
+			}
+		}
+
+
+		if (moduleSourceMap) {
+			moduleSourceMap = JSON.parse(moduleSourceMap);
+
+			// Check for index map, which is currently not supported
+			if (Array.isArray(moduleSourceMap.sections)) {
+				log.warn(
+					`Module ${moduleName} references an index source map which is currently not supported. ` +
+					`A transient source map will be created instead...`
+				);
+				moduleSourceMap = createTransientSourceMap({
+					moduleName: path.posix.basename(resourcePath),
+					moduleContent
+				});
+			}
+		} else {
+			log.verbose(`No source map available for module ${moduleName}. Creating transient source map...`);
+			moduleSourceMap = createTransientSourceMap({
+				moduleName: path.posix.basename(resourcePath),
+				moduleContent
+			});
+		}
+
+		return {
+			moduleSourceMap,
+			moduleContent: newModuleContent
+		};
+	}
+}
+
+const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"];
+
+/*
+ * @param {object} parameters
+ * @param {string} parameters.moduleName
+ * @param {string} parameters.moduleContent
+ * @param {object} [parameters.moduleSourceMap]
+ * @returns {Promise} Object containing moduleContent and
+ * 	moduleSourceMap (if one was provided) or null if no rewrite was applicable
+ */
+async function rewriteDefine({moduleName, moduleContent, moduleSourceMap}) {
+	let ast;
+	try {
+		ast = parseJS(moduleContent, {range: true});
+	} catch (e) {
+		log.error(`Error while parsing ${moduleName}: ${e.message}`);
+		log.verbose(e.stack);
+		return {};
+	}
+
+	if ( ast.type === Syntax.Program &&
+			ast.body.length === 1 && ast.body[0].type === Syntax.ExpressionStatement &&
+			isMethodCall(ast.body[0].expression, CALL_SAP_UI_DEFINE) ) {
+		const changes = [];
+		const defineCall = ast.body[0].expression;
+
+		// Inject module name if missing
+		if ( defineCall.arguments.length == 0 ||
+			![Syntax.Literal, Syntax.TemplateLiteral].includes(defineCall.arguments[0].type)) {
+			let value = `"${toRequireJSName(moduleName)}"`;
+			let index;
+
+			if (defineCall.arguments.length == 0) {
+				// asterisk marks the index: sap.ui.define(*)
+				index = defineCall.range[1] - 1;
+			} else {
+				// asterisk marks the index: sap.ui.define(*argument1)
+				index = defineCall.arguments[0].range[0];
+				value += ", ";
+			}
+
+			changes.push({
+				index,
+				value
+			});
+		}
+
+		// rewrite sap.ui.define to sap.ui.predefine
+		if ( defineCall.callee.type === Syntax.MemberExpression &&
+				defineCall.callee.property.type === Syntax.Identifier &&
+				defineCall.callee.property.name === "define" ) {
+			changes.push({
+				// asterisk marks the index: sap.ui.*define()
+				index: defineCall.callee.property.range[0],
+				value: "pre"
+			});
+		}
+
+		return transform(changes, moduleContent, moduleSourceMap);
+	}
+
+	return null;
+}
+
+/*
+ * @param {object[]} changes Changes that should be applied to the code
+ * @param {string} moduleContent Code to transform
+ * @param {object} [moduleSourceMap] Optional source map that should be aligned with the content change
+ * @returns {Promise} Object containing moduleContent and
+ * 								moduleSourceMap (if one was provided)
+ */
+async function transform(changes, moduleContent, moduleSourceMap) {
+	const mappingChanges = [];
+
+	const array = Array.from(moduleContent);
+	// No sorting needed as changes are added in correct (reverse) order
+	changes.forEach((change) => {
+		if (moduleSourceMap) {
+			// Compute line and column for given index to re-align source map with inserted characters
+			const precedingCode = array.slice(0, change.index);
+
+			const line = precedingCode.reduce((lineCount, char) => {
+				if (char === "\n") {
+					lineCount++;
+				}
+				return lineCount;
+			}, 0);
+			const lineStartIndex = precedingCode.lastIndexOf("\n") + 1;
+			const column = change.index - lineStartIndex;
+
+			// Source map re-alignment needs to be done from front to back
+			mappingChanges.unshift({
+				line,
+				column,
+				columnDiff: change.value.length
+			});
+		}
+
+		// Apply modification
+		array.splice(
+			change.index,
+			0,
+			change.value
+		);
+	});
+	const transformedCode = array.join("");
+
+	if (moduleSourceMap) {
+		const mappings = decodeMappings(moduleSourceMap.mappings);
+		mappingChanges.forEach((mappingChange) => {
+			const lineMapping = mappings[mappingChange.line];
+			if (!lineMapping) {
+				// No mapping available that could be transformed
+				return;
+			}
+			// Mapping structure:
+			// [generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex]
+			lineMapping.forEach((mapping) => {
+				if (mapping[0] > mappingChange.column) {
+					// All column mappings for the generated code after any change
+					// need to be moved by the amount of inserted characters
+					mapping[0] = mapping[0] + mappingChange.columnDiff;
+				}
+			});
+		});
+
+		moduleSourceMap.mappings = encodeMappings(mappings);
+
+		// No need for file information in source map since the bundled code does not exist in any file anyways
+		delete moduleSourceMap.file;
+	}
+
+	return {
+		moduleContent: transformedCode,
+		moduleSourceMap
+	};
+}
+
+function createTransientSourceMap({moduleName, moduleContent, includeContent = false}) {
+	const sourceMap = {
+		version: 3,
+		names: [],
+		sources: [moduleName],
+		// TODO: check whether moduleContent.match() with \n is better w.r.t performance/memory usage
+		mappings: encodeMappings(moduleContent.split("\n").map((line, i) => {
+			return [[0, 0, i, 0]];
+		}))
+	};
+	if (includeContent) {
+		sourceMap.sourcesContent = [moduleContent];
+	}
+	return sourceMap;
+}
+
+export default BundleBuilder;
+
+export const __localFunctions__ = (process.env.NODE_ENV === "test") ?
+	{rewriteDefine, createTransientSourceMap} : undefined;
diff --git a/packages/builder/lib/lbt/bundle/BundleDefinition.js b/packages/builder/lib/lbt/bundle/BundleDefinition.js
new file mode 100644
index 00000000000..bcb617267e1
--- /dev/null
+++ b/packages/builder/lib/lbt/bundle/BundleDefinition.js
@@ -0,0 +1,38 @@
+export const SectionType = {
+	/**
+	 * The modules are assumed to exist already in the loading environment.
+	 */
+	Provided: "provided",
+
+	/**
+	 * The modules are to be written 1:1 into the resulting module.
+	 */
+	Raw: "raw",
+
+	/**
+	 * The modules are wrapped into a call to jQuery.sap.registerPreload(),
+	 * each Javascript module will be embedded in an anonymous function.
+	 */
+	Preload: "preload",
+
+	/**
+	 * Content information for another bundle is written.
+	 * Requires UI5 version 1.74.0 which adds runtime support for the 'bundles' and 'bundlesUI5'
+	 * ui5loader configuration.
+	 */
+	BundleInfo: "bundleInfo",
+
+	/**
+	 * For each module, a require call will be created.
+	 * Usually used as the last section in a merged module to enforce loading and
+	 * execution of some specific module or modules.
+	 */
+	Require: "require",
+
+	/**
+	 * Dependency cache information that lists modules and their dependencies
+	 * of all types: JS, declarative views/fragments.
+	 * Only the dependencies of the modules are stored as 'depCache' configuration.
+	 */
+	DepCache: "depCache"
+};
diff --git a/packages/builder/lib/lbt/bundle/BundleWriter.js b/packages/builder/lib/lbt/bundle/BundleWriter.js
new file mode 100644
index 00000000000..c6b8a22cded
--- /dev/null
+++ b/packages/builder/lib/lbt/bundle/BundleWriter.js
@@ -0,0 +1,103 @@
+
+
+const NL = "\n";
+const ENDS_WITH_NEW_LINE = /(\r\n|\r|\n)[ \t]*$/;
+const SPACES_OR_TABS_ONLY = /^[ \t]+$/;
+
+/**
+ * A filtering writer that can count written chars and provides some convenience
+ * methods when writing Javascript files.
+ *
+ * Most methods have been extracted from JSMergeWriter.
+ *
+ * columnOffset and lineOffset are used for sourcemap merging as reference to where we are at a given point in time
+ *
+ * @author Frank Weigel
+ * @since 1.27.0
+ * @private
+ */
+class BundleWriter {
+	constructor() {
+		this.buf = "";
+		this.lineOffset = 0;
+		this.columnOffset = 0;
+		this.segments = [];
+		this.currentSegment = null;
+		this.currentSourceIndex = 0;
+		this.endsWithNewLine = true; // Initially we don't need a new line
+	}
+
+	write(...str) {
+		let writeBuf = "";
+		for ( let i = 0; i < str.length; i++ ) {
+			writeBuf += str[i];
+			if (str[i] != null && str[i].split) {
+				const strSplit = str[i].split(NL);
+				this.lineOffset += strSplit.length - 1;
+				this.columnOffset += strSplit[strSplit.length - 1].length;
+			}
+		}
+		if ( writeBuf.length >= 1 ) {
+			this.buf += writeBuf;
+			this.endsWithNewLine =
+				ENDS_WITH_NEW_LINE.test(writeBuf) ||
+				(this.endsWithNewLine && SPACES_OR_TABS_ONLY.test(writeBuf));
+		}
+	}
+
+	writeln(...str) {
+		for ( let i = 0; i < str.length; i++ ) {
+			this.buf += str[i];
+			if (str[i] != null && str[i].split) {
+				const strSplit = str[i].split(NL);
+				this.lineOffset += strSplit.length - 1;
+			}
+		}
+		this.buf += NL;
+		this.endsWithNewLine = true;
+		this.lineOffset += 1;
+		this.columnOffset = 0;
+	}
+
+	ensureNewLine() {
+		if ( !this.endsWithNewLine ) {
+			this.buf += NL;
+			this.endsWithNewLine = true;
+			this.lineOffset += 1;
+			this.columnOffset = 0;
+		}
+	}
+
+	toString() {
+		return this.buf;
+	}
+
+	get length() {
+		return this.buf.length;
+	}
+
+	startSegment(module) {
+		if ( this.currentSegment ) {
+			throw new Error("trying to start a segment while another segment is still open");
+		}
+		this.currentSegment = {
+			module: module,
+			startIndex: this.length,
+		};
+		this.currentSourceIndex = this.segments.length;
+	}
+
+	endSegment() {
+		if ( !this.currentSegment ) {
+			throw new Error("trying to end a segment while no segment is open");
+		}
+		this.currentSegment.endIndex = this.length;
+		this.segments.push(this.currentSegment);
+		const targetSize = this.currentSegment.endIndex - this.currentSegment.startIndex;
+		this.currentSegment = null;
+		this.currentSourceIndex = -1;
+		return targetSize;
+	}
+}
+
+export default BundleWriter;
diff --git a/packages/builder/lib/lbt/bundle/ResolvedBundleDefinition.js b/packages/builder/lib/lbt/bundle/ResolvedBundleDefinition.js
new file mode 100644
index 00000000000..0056eb9748e
--- /dev/null
+++ b/packages/builder/lib/lbt/bundle/ResolvedBundleDefinition.js
@@ -0,0 +1,108 @@
+
+import {MODULE__JQUERY_SAP_GLOBAL, MODULE__SAP_UI_CORE_CORE} from "../UI5ClientConstants.js";
+import ModuleInfo from "../resources/ModuleInfo.js";
+import {SectionType} from "./BundleDefinition.js";
+
+class ResolvedBundleDefinition {
+	constructor( bundleDefinition /* , vars*/) {
+		this.bundleDefinition = bundleDefinition;
+		this.name = bundleDefinition.name;
+		// NODE-TODO (ModuleName) ModuleNamePattern.resolvePlaceholders(bundleDefinition.getName(), vars);
+		this.sections = bundleDefinition.sections.map(
+			(sectionDefinition) => new ResolvedSection(this, sectionDefinition)
+		);
+	}
+
+	get containsCoreSync() {
+		return this.sections.some(
+			(section) =>
+				(section.mode === SectionType.Raw ||
+					(section.mode === SectionType.Require && section.async === false)) &&
+				section.modules.some((module) => module === MODULE__SAP_UI_CORE_CORE)
+		);
+	}
+
+	get containsGlobal() {
+		return this.sections.some(
+			(section) =>
+				section.mode === SectionType.Raw &&
+				section.modules.some((module) => module === MODULE__JQUERY_SAP_GLOBAL)
+		);
+	}
+
+	executes(moduleName) {
+		return this.sections.some(
+			(section) =>
+				(section.mode === SectionType.Raw || section.mode === SectionType.Require) &&
+				section.modules.some((module) => module === moduleName)
+		);
+	}
+
+	createModuleInfo(pool, allowStringBundling) {
+		const bundleInfo = new ModuleInfo();
+		bundleInfo.name = this.name;
+
+		let promise = Promise.resolve(true);
+		this.sections.forEach( (section) => {
+			promise = promise.then( () => {
+				if ( section.mode === SectionType.Provided ) {
+					return;
+				}
+				if ( section.mode === SectionType.Require ) {
+					section.modules.forEach( (module) => bundleInfo.addDependency(module) );
+					return;
+				}
+				if ( section.mode == SectionType.Raw && section.modules.length ) {
+					// if a bundle contains raw modules, it is a raw module itself
+					bundleInfo.rawModule = true;
+				}
+				let modules = section.modules;
+				if ( section.mode === SectionType.Preload ) {
+					modules = section.modules.slice();
+					modules.sort();
+				}
+
+				return Promise.all(
+					modules.map( (submodule) => {
+						return pool.getModuleInfo(submodule).then(
+							(subinfo) => {
+								if (!bundleInfo.subModules.includes(subinfo.name) &&
+									(!subinfo.requiresTopLevelScope ||
+										(subinfo.requiresTopLevelScope && allowStringBundling))) {
+									bundleInfo.addSubModule(subinfo);
+								}
+							}
+						);
+					})
+				);
+			});
+		});
+
+		return promise.then( () => bundleInfo );
+	}
+}
+
+class ResolvedSection {
+	constructor(bundle, sectionDefinition) {
+		this.bundle = bundle;
+		this.sectionDefinition = sectionDefinition;
+	}
+
+	get mode() {
+		return this.sectionDefinition.mode;
+	}
+
+	get name() {
+		return this.sectionDefinition.name;
+	}
+
+	get declareRawModules() {
+		return this.sectionDefinition.declareRawModules;
+	}
+
+	get async() {
+		return this.sectionDefinition.async;
+	}
+}
+
+export default ResolvedBundleDefinition;
diff --git a/packages/builder/lib/lbt/bundle/Resolver.js b/packages/builder/lib/lbt/bundle/Resolver.js
new file mode 100644
index 00000000000..45c3e7f3222
--- /dev/null
+++ b/packages/builder/lib/lbt/bundle/Resolver.js
@@ -0,0 +1,368 @@
+/**
+ * Takes a bundle definition and resolves it against the given pool.
+ */
+
+import topologicalSort from "../graph/topologicalSort.js";
+import {getRendererName} from "../UI5ClientConstants.js";
+import ResourceFilterList from "../resources/ResourceFilterList.js";
+import {SectionType} from "./BundleDefinition.js";
+import ResolvedBundleDefinition from "./ResolvedBundleDefinition.js";
+import {getLogger} from "@ui5/logger";
+const log = getLogger("lbt:bundle:Resolver");
+
+let dependencyTracker;
+
+const DEFAULT_FILE_TYPES = [
+	".js",
+	".control.xml", // XMLComposite
+	".fragment.html",
+	".fragment.json",
+	".fragment.xml",
+	".view.html",
+	".view.json",
+	".view.xml"
+];
+
+/**
+ * Resolve a bundle definition.
+ *
+ * - evaluate include / exclude filters for each section
+ * - follow dependencies, if option 'resolve' is configured for a section
+ *
+ * TODO ModuleResolver changes the order of the configured modules even if resolve isn't true
+ *
+ * @private
+ */
+class BundleResolver {
+	// private final Trace trace;
+	constructor(pool) {
+		this.pool = pool;
+	}
+	// NODE-TODO private final Map moduleDefinitions;
+
+	/**
+	 * @param {ModuleDefinition} bundle Bundle definition to resolve
+	 			List of default file types to which a prefix pattern shall be expanded.
+	 * @returns {Promise}
+	 */
+	resolve(bundle) {
+		const fileTypes = bundle.defaultFileTypes || DEFAULT_FILE_TYPES;
+		let visitedResources = Object.create(null);
+		let selectedResources = Object.create(null);
+		let selectedResourcesSequence = [];
+		const pool = this.pool;
+		/**
+		 * Names of modules that are required in some way but could not be found
+		 * in the resource pool.
+		 */
+		const missingModules = Object.create(null);
+		/**
+		 * Names of modules that are included in non-decomposable bundles.
+		 * If they occur in the missingModules, then this is not an error.
+		 */
+		const includedModules = new Set();
+
+		/**
+		 * @param {JSModuleSectionDefinition} section
+		 * @returns {Collection}
+		 */
+		function collectModulesForSection(section) {
+			let prevLength;
+			let newKeys;
+
+			// NODE-TODO resolvePlaceholders(section.getFilters());
+			const filters = new ResourceFilterList( section.filters, fileTypes );
+
+			function isAccepted(resourceName, required) {
+				let match = required;
+				// evaluate module filters only when global filters match
+				match = filters.matches(resourceName, required); // NODE-TODO filter.matches(name, match, required);
+				return match;
+			}
+
+			function checkForDecomposableBundle(resource) {
+				const isBundle =
+					resource?.info?.subModules.length > 0 &&
+					!/(?:^|\/)library.js$/.test(resource.info.name);
+
+				if (!isBundle) {
+					return {
+						resource,
+						isBundle,
+						decomposable: false
+					};
+				}
+
+				return Promise.all(
+					resource.info.subModules.map((sub) => pool.findResource(sub).catch(() => false))
+				).then((modules) => {
+					// it might look more natural to expect 'all' embedded modules to exist in the pool,
+					// but expecting only 'some' module to exist is a more conservative approach
+					return {
+						resource,
+						isBundle,
+						decomposable: modules.some(($) => ($))
+					};
+				});
+			}
+
+			function checkAndAddResource(resourceName, depth, msg) {
+				// console.log("    checking " + resourceName + " at depth " + depth);
+				let maybeAccepted = true;
+				let done;
+
+				if ( !(resourceName in visitedResources) && (maybeAccepted = isAccepted(resourceName, depth > 0)) ) {
+					// console.log("    accepted: " + resourceName );
+					if ( dependencyTracker != null ) {
+						dependencyTracker.visitDependency(resourceName);
+					}
+
+					// remember that we have seen this module already
+					visitedResources[resourceName] = resourceName;
+
+					done = pool.findResourceWithInfo(resourceName)
+						.catch( (err) => {
+							// if the caller provided an error message, log it
+							if ( msg ) {
+								missingModules[resourceName] ??= [];
+								missingModules[resourceName].push(msg);
+							}
+							// return undefined
+						})
+						.then( (resource) => checkForDecomposableBundle(resource) )
+						.then( ({resource, isBundle, decomposable}) => {
+							const dependencyInfo = resource && resource.info;
+							let promises = [];
+
+							if ( isBundle && !decomposable ) {
+								resource.info.subModules.forEach(
+									(included) => {
+										includedModules.add(included);
+									}
+								);
+							}
+
+							if ( decomposable ) {
+								// bundles are not added, only their embedded modules
+								promises = dependencyInfo.subModules.map( (included) => {
+									return checkAndAddResource(included, depth + 1,
+										`**** error: missing submodule ${included}, included by ${resourceName}`);
+								});
+							} else if ( resource != null ) {
+								// trace.trace("    checking dependencies of " + resource.name );
+								selectedResources[resourceName] = resourceName;
+								selectedResourcesSequence.push(resourceName);
+
+								// trace.info("    collecting %s", resource.name);
+
+								// add dependencies, if 'resolve' is configured
+								if ( section.resolve && dependencyInfo ) {
+									promises = dependencyInfo.dependencies.map( function(required) {
+										// ignore conditional dependencies if not configured
+										if ( !section.resolveConditional &&
+												dependencyInfo.isConditionalDependency(required) ) {
+											return;
+										}
+
+										return checkAndAddResource(required, depth + 1,
+											`**** error: missing module ${required}, required by ${resourceName}`);
+									});
+								}
+
+								// add renderer, if 'renderer' is configured and if it exists
+								if ( section.renderer ) {
+									const rendererModuleName = getRendererName( resourceName );
+									promises.push( checkAndAddResource( rendererModuleName, depth + 1) );
+								}
+							}
+
+							return Promise.all( promises.filter( ($) => $ ) );
+						});
+
+					if ( dependencyTracker != null ) {
+						dependencyTracker.endVisitDependency(resourceName);
+					}
+				} else if ( dependencyTracker != null && maybeAccepted && isAccepted(resourceName, depth>0) ) {
+					// Note: the additional 'maybeAccepted' condition avoids calling the expensive 'isAccepted'
+					// twice if it already returned false in the 'if' condition
+
+					dependencyTracker.visitDependencyAgain(resourceName);
+
+					done = Promise.resolve(true);
+				}
+
+				return done;
+			}
+
+			let oldSelectedResources;
+			let oldIgnoredResources;
+			let oldSelectedResourcesSequence;
+
+			if ( [SectionType.Require, SectionType.DepCache].includes(section.mode) ) {
+				oldSelectedResources = selectedResources;
+				oldIgnoredResources = visitedResources;
+				oldSelectedResourcesSequence = selectedResourcesSequence;
+				selectedResources = Object.create(null);
+				selectedResourcesSequence = [];
+				visitedResources = Object.create(null);
+			} else {
+				// remember current state of module collection - needed to determine difference set later
+				prevLength = selectedResourcesSequence.length;
+			}
+
+			/*
+			 * In the Maven version of the bundle tooling, it was possible to define the content
+			 * of a section of type 'provided' by listing a set of other bundle definition files.
+			 * The whole content of those bundles then was determined and excluded from the current bundle.
+			 *
+			 * In the NodeJS version of the tooling, this is not supported. Instead, the resulting JS file for
+			 * a bundle can be specified and the dependency analysis will determine the content of the bundle
+			 * and exclude it from the current bundle.
+			 *
+			if ( section.mode == SectionType.Provided && section.modules ) {
+				throw new Error("unsupported");
+				for(ModuleName providedModuleDefinitionName : section.getProvidedModules()) {
+					AbstractModuleDefinition providedModuleDefinition =
+						moduleDefinitions.get(providedModuleDefinitionName);
+					if ( providedModuleDefinition instanceof JSModuleDefinition ) {
+						trace.verbose("    resolving provided module %s", providedModuleDefinitionName);
+						ModuleResolver resolver = new ModuleResolver(trace, pool, moduleDefinitions, null);
+						ResolvedBundleDefinition resolved = resolver.run((JSModuleDefinition) providedModuleDefinition,
+							placeholderValues);
+						for(ResolvedBundleDefinitionSection resolvedSection : resolved.getSections()) {
+							if ( resolvedSection.getMode() != SectionType.Require ) {
+								for(ModuleName providedModuleName : resolvedSection.getModules()) {
+									ModuleInfo providedModuleInfo = pool.getModuleInfo(providedModuleName);
+									if ( providedModuleInfo != null &&
+											!visitedModules.containsKey(providedModuleName) ) {
+										visitedModules.put(providedModuleName, providedModuleInfo);
+									}
+								}
+							}
+						}
+					} else {
+						trace.error("provided module could not be found or is not a JS module : %s",
+							providedModuleDefinitionName);
+					}
+				}
+			}
+			*/
+
+			// scan all known resources
+			const promises = pool.resources.map( function(resource) {
+				return checkAndAddResource(resource.name, 0);
+			});
+
+			return Promise.all(promises).then( function() {
+				if ( [SectionType.Require, SectionType.DepCache].includes(section.mode) ) {
+					newKeys = selectedResourcesSequence;
+					selectedResources = oldSelectedResources;
+					visitedResources = oldIgnoredResources;
+					selectedResourcesSequence = oldSelectedResourcesSequence;
+				} else {
+					newKeys = selectedResourcesSequence.slice( prevLength ); // preserve order (for raw sections)
+				}
+
+				// console.log("    resolved module set: %s", newKeys);
+				return newKeys;
+			});
+		}
+
+		/*
+		 * In the Maven version of the bundle tooling, a bundle definition could be
+		 * parameterized by locale, ltr/rtl mode and theme. The LBT doesn't support this yet.
+		 *
+		 * As theming files are build with less now, only the parameterization by locale
+		 * might be needed. It's lack can be compensated by programmatically building the
+		 * necessary bundle definitions and e.g. injecting the locale into the Id or the
+		 * filters defining the content of the bundle.
+		 * .
+		private Collection resolvePlaceholders(Collection list) {
+			if ( !placeholderValues.isEmpty() ) {
+				List modifiedList = new ArrayList(list);
+				for(int i=0; i {
+						if ( section.mode == SectionType.Raw && section.sort !== false ) {
+							// sort the modules in topological order
+							return topologicalSort(pool, modules).then( (modules) => {
+								log.silly(`      Resolved modules (sorted): ${modules}`);
+								return modules;
+							});
+						}
+						if ( section.mode === SectionType.BundleInfo ) {
+							modules.sort();
+						}
+						log.silly(`      Resolved modules: ${modules}`);
+						return modules;
+					}).then( function(modules) {
+						resolvedSection.modules = modules;
+					});
+				// NODE-TODO long t1=System.nanoTime();
+
+				// NODE-TODO if ( PerfMeasurement.ACTIVE ) trace.info("[Measurement] %12d nsec - %s", t1-t0,
+				// 												"Module collection and filtering");
+			});
+		});
+
+		if ( dependencyTracker != null ) {
+			dependencyTracker.endResolution(bundle, /* NODE-TODO, vars*/);
+		}
+
+		// NODE-TODO if ( PerfMeasurement.ACTIVE ) PerfMeasurement.stop(PerfKeys.RESOLVE_MODULE);
+
+		return previous.then( function() {
+			// ignore missing modules that have been found in non-decomposable bundles
+			includedModules.forEach((included) => delete missingModules[included]);
+
+			// report the remaining missing modules
+			Object.keys(missingModules).sort().forEach((missing) => {
+				const messages = missingModules[missing];
+				messages.sort().forEach((msg) => {
+					log.error(msg);
+				});
+			});
+
+			log.verbose("  Resolving bundle done");
+
+			return resolved;
+		});
+	}
+}
+
+
+export default BundleResolver;
+
diff --git a/packages/builder/lib/lbt/calls/SapUiDefine.js b/packages/builder/lib/lbt/calls/SapUiDefine.js
new file mode 100644
index 00000000000..0bf1c515e2f
--- /dev/null
+++ b/packages/builder/lib/lbt/calls/SapUiDefine.js
@@ -0,0 +1,96 @@
+
+import {Syntax} from "../utils/parseUtils.js";
+import {resolveRelativeRequireJSName} from "../utils/ModuleName.js";
+import {isBoolean, getStringValue} from "../utils/ASTUtils.js";
+
+class SapUiDefineCall {
+	constructor(node, moduleName) {
+		this.node = node;
+		this.name = moduleName;
+		this.dependencyArray = null;
+		this.factory = null;
+		this.exportAsGlobal = false;
+		this.paramNames = null;
+
+		const args = node.arguments;
+		if ( args == null ) {
+			return;
+		}
+
+		// Note: the following code assumes that no variables or expressions are used
+		// for the arguments of the sap.ui.define call. The analysis could be made more
+		// sophisticated and could try to skip unhandled parameter types, based on the
+		// AST type of follow-up arguments.
+		// But on the other hand, an incomplete analysis of the define call is useless in
+		// many cases, so it might not be worth the effort.
+
+		let i = 0;
+		let params;
+
+		const name = getStringValue(args[i]);
+		if ( i < args.length && name ) {
+			// assert(String)
+			this.name = name;
+			i++;
+		}
+
+		if ( i < args.length && args[i].type === Syntax.ArrayExpression ) {
+			this.dependencyArray = args[i++];
+			this.dependencies = this.dependencyArray.elements.map( (elem) => {
+				const value = getStringValue(elem);
+				if ( !value ) {
+					throw new TypeError();
+				}
+				return resolveRelativeRequireJSName(this.name, value);
+			});
+			this.dependencyInsertionIdx = this.dependencyArray.elements.length;
+		}
+
+		if ( i < args.length && (
+			args[i].type === Syntax.FunctionExpression || args[i].type === Syntax.ArrowFunctionExpression)
+		) {
+			this.factory = args[i++];
+			params = this.factory.params;
+			this.paramNames = params.map( (param) => {
+				if ( param.type !== Syntax.Identifier ) {
+					return null;
+				}
+				return param.name;
+			});
+			if ( this.factory.params.length < this.dependencyInsertionIdx ) {
+				this.dependencyInsertionIdx = this.factory.params.length;
+			}
+		}
+
+		if ( i < args.length && isBoolean(args[i]) ) {
+			this.exportAsGlobal = args[i].value;
+		}
+
+		// console.log("declared dependencies: " + this.dependencies);
+	}
+
+	/* NODE-TODO: 'b' is not defined
+	addDependency(module, shortcut) {
+		if ( !this.dependencyArray ) {
+			throw new Error("no dependency array"); // TODO create
+			// console.error("no dependency error");
+			// return;
+		}
+		let i = this.dependencyInsertionIdx++;
+		this.dependencyArray.elements.splice(i, 0, b.literal(module));
+		this.dependencies.splice(i, 0, module);
+		// console.log(this.factory.params);
+		this.factory.params.splice(i, 0, b.identifier(shortcut));
+		this.paramNames.splice(i, 0, shortcut);
+	}*/
+
+	findImportName(module) {
+		const idx = this.dependencies ? this.dependencies.indexOf(module) : -1;
+		if ( idx >= 0 ) {
+			return this.paramNames[idx];
+		}
+		return null;
+	}
+}
+
+export default SapUiDefineCall;
diff --git a/packages/builder/lib/lbt/graph/dependencyGraph.js b/packages/builder/lib/lbt/graph/dependencyGraph.js
new file mode 100644
index 00000000000..3ff1c82f420
--- /dev/null
+++ b/packages/builder/lib/lbt/graph/dependencyGraph.js
@@ -0,0 +1,70 @@
+import {getLogger} from "@ui5/logger";
+const log = getLogger("lbt:graph:dependencyGraph");
+
+class Node {
+	constructor(name) {
+		this.name = name;
+		this.visited = false;
+		this.succ = new Set();
+		this.pred = new Set();
+		this.dominators = null; // TODO not nice to know dominators here
+	}
+}
+
+/**
+ *
+ * @param {ResourcePool} pool resource pool
+ * @param {Array} roots root elements
+ * @param {object} options Options
+ * @param {boolean} options.includeConditionalDependencies whether or not to include optional dependencies
+ * @returns {Promise<{n0: Node, nodes: Map}>}
+ */
+async function createDependencyGraph(pool, roots, options) {
+	const includeConditionalDependencies = options && options.includeConditionalDependencies;
+
+	// create graph representing modules and their dependencies
+	const nodes = new Map();
+
+	function visitNode(name) {
+		let node = nodes.get(name);
+		if ( !node ) {
+			nodes.set( name, node = new Node(name) );
+		} else if ( node.visited ) {
+			return Promise.resolve(node);
+		}
+
+		node.visited = true;
+
+		return pool.getModuleInfo( name ).then( (module) => {
+			const p = module.dependencies.map( (dep) => {
+				if ( includeConditionalDependencies || !module.isConditionalDependency(dep) ) {
+					return visitNode(dep).then( (child) => child.pred.add( node ) );
+				}
+			});
+			return Promise.all(p);
+		}, (err) => {
+			log.error(`Module ${name} not found in pool: ${err.message}`);
+		}).then( () => node );
+	}
+
+	// create artificial root node and link it with roots
+	const n0 = new Node("");
+	nodes.set("", n0);
+	const p = roots.map( (root) => {
+		// console.log("  entry point %s", root.name);
+		return visitNode(root.name).then( (child) => child.pred.add( n0 ) );
+	});
+
+	return Promise.all(p).then( () => {
+		return {
+			n0: n0,
+			nodes: nodes
+		};
+	});
+}
+
+export default createDependencyGraph;
+
+// TODO introduce class Graph
+// TODO remove n0 here, only introduce it in dominator tree (using the Graph API)
+// TODO introduce payload for nodes to get rid of visited or dominator
diff --git a/packages/builder/lib/lbt/graph/dominatorTree.js b/packages/builder/lib/lbt/graph/dominatorTree.js
new file mode 100644
index 00000000000..56dc87607ee
--- /dev/null
+++ b/packages/builder/lib/lbt/graph/dominatorTree.js
@@ -0,0 +1,118 @@
+import {getLogger} from "@ui5/logger";
+const log = getLogger("lbt:graph:dominatorTree");
+
+/**
+ * Creates a dependency graph starting from the given set of root modules.
+ * An artificial node is added to the graph which depends on all given root modules.
+ * The graph is then reduced to its dominator tree (starting with the artificial root node).
+ *
+ * The algorithm used to calculate the dominator tree is the naive implementation
+ * described in https://en.wikipedia.org/wiki/Dominator_(graph_theory) .
+ * It has runtime O(n^2) and could be replaced by a more sophisticated one (e.g. Lengauer and Tarjan).
+ *
+ * @param {object} graph Graph to calculate the DOM tree for
+ * @param {Map.} graph.nodes Nodes of the dependency graph
+ * @param {Node} graph.n0 Artificial root node
+ * @returns {Node} Root node of the dominator tree.
+ * @private
+ */
+function calculateDominatorTree({n0, nodes}) {
+	// initialize set of dominators for each node
+	for ( const n of nodes.values() ) {
+		if ( n === n0 ) {
+			// dominator of the start node is the start node itself
+			n.dominators = new Set().add(n);
+		} else {
+			// for all other nodes, set all nodes as the dominators
+			n.dominators = new Set(nodes.values()); // set is modified later, therefore each node gets its own copy
+		}
+	}
+
+	log.verbose(`${nodes.size - 1} modules found, starting dominator tree calculation`);
+
+	function countEdges(nodes) {
+		let count = 0;
+		for ( const n of nodes.values() ) {
+			count += n.dominators.size;
+		}
+		return count;
+	}
+
+	// iteratively eliminate nodes that are not dominators
+	let modified;
+	do {
+		// while changes in any Dom(n)
+
+		if (log.isLevelEnabled("verbose")) {
+			log.verbose(`${countEdges(nodes)} remaining edges`);
+		}
+
+		modified = false;
+		for ( const n of nodes.values() ) {
+			if ( n === n0 ) {
+				continue; // no processing for the root node
+			}
+			// Dom(n) = {n} union with intersection over Dom(p) for all p in pred(n) */
+			for ( const d of n.dominators ) {
+				if ( d === n ) {
+					continue; // by definition, n is always a dominator of its own
+				}
+				// Note: the intersection can only remove items that are currently in the set of
+				// dominators of n, it never will add new nodes. Therefore the intersection
+				// is implemented by checking each current dominator of n whether it is contained
+				// in the dominator sets of all predecessors. If not, it is removed from the
+				// set of dominotors of n.
+				for ( const p of n.pred ) {
+					if ( !p.dominators.has(d) ) {
+						n.dominators.delete(d);
+						modified = true;
+						break;
+					}
+				}
+			}
+		}
+	} while (modified);
+
+	// build the inverse of the 'strictly-dominated-by' graph ('strictly-dominates' graph)
+	for ( const n of nodes.values() ) {
+		for ( const d of n.dominators ) {
+			if ( d !== n ) { // ignore edge to self ('strict')
+				d.succ.add(n);
+			}
+		}
+		n.visited = false;
+	}
+
+	log.verbose("Reduce dominator graph to immediate dominator tree");
+
+	// use DFS to reduce the dominator graph to the (immediate) dominator tree
+	//
+	// When after visiting all dominated nodes (=== 'succ'), one of those nodes is not yet
+	// marked as immediately dominated (=== 'visited'), then the current node is the
+	// immediate dominator, the corresponding edge is kept (otherwise removed) and the child
+	// is marked as 'immediately dominated'
+	function dfs(node) {
+		// visit children, thereby identifying non-immediately dominated nodes
+		for ( const child of node.succ ) {
+			if ( !child.visited ) {
+				// console.log("visit %s->%s (visited:%s)", node.name, child.name, child.visited);
+				dfs(child);
+			}
+		}
+		for ( const child of node.succ ) {
+			if ( child.visited ) {
+				// console.log("delete %s -> %s", node.name, child.name)
+				node.succ.delete(child);
+			} else {
+				child.visited = true;
+			}
+		}
+	}
+	dfs(n0);
+
+	log.verbose("Calculation of dominator tree done");
+
+	return n0;
+}
+
+export default calculateDominatorTree;
diff --git a/packages/builder/lib/lbt/graph/topologicalSort.js b/packages/builder/lib/lbt/graph/topologicalSort.js
new file mode 100644
index 00000000000..6954ea26678
--- /dev/null
+++ b/packages/builder/lib/lbt/graph/topologicalSort.js
@@ -0,0 +1,189 @@
+
+import {getLogger} from "@ui5/logger";
+const log = getLogger("lbt:graph:topologicalSort");
+
+/**
+ * Represents a module and its dependencies in a dependency graph.
+ * Manages outgoing references as well as incoming references (if required).
+ *
+ * @author Frank Weigel
+ * @private
+ */
+class GraphNode {
+	constructor(name, indegreeOnly) {
+		this.name = name;
+		// for the minSpanningSet, only the indegree is needed
+		this.outgoing = [];
+		if ( !indegreeOnly ) {
+			this.incoming = [];
+		}
+		this.indegree = 0;
+	}
+
+	toString() {
+		return "GraphNode " + this.name +
+			"(outgoing=" + this.outgoing.map(($) => $.name) +
+			", incoming=" + this.incoming.map(($) => $.name) + ")";
+	}
+}
+
+/**
+ * Creates a dependency graph from the given moduleNames.
+ * Ignores modules not in the pool
+ *
+ * @param {ResourcePool} pool
+ * @param {string[]} moduleNames
+ * @param {boolean} indegreeOnly
+ * @returns {Promise}
+ * @private
+ */
+function createDependencyGraph(pool, moduleNames, indegreeOnly) {
+	const graph = Object.create(null);
+
+	const promises = moduleNames.map( (moduleName) => {
+		return pool.getModuleInfo(moduleName).
+			then( (module) => {
+				let node = graph[moduleName];
+				if ( node == null ) {
+					node = new GraphNode(moduleName, indegreeOnly);
+					graph[moduleName] = node;
+				}
+				const p = module.dependencies.map( function(dep) {
+					if ( module.isConditionalDependency(dep) ) {
+						return;
+					}
+					return pool.getModuleInfo(dep).then( (depModule) => {
+						if ( moduleNames.indexOf(dep) >= 0 ) {
+							let depNode = graph[dep];
+							if ( depNode == null ) {
+								depNode = new GraphNode(dep, indegreeOnly);
+								graph[dep] = depNode;
+							}
+							node.outgoing.push(depNode);
+							if ( indegreeOnly ) {
+								depNode.indegree++;
+							} else {
+								depNode.incoming.push(node);
+							}
+						}
+					}, (erro) => null);
+				});
+				return Promise.all(p);
+			}, (err) => {
+				log.error(`Module ${moduleName} not found in pool`);
+			});
+	});
+
+	return Promise.all(promises).then(function() {
+		// if ( trace.isTrace() ) trace.trace("initial module dependency graph: %s", dumpGraph(graph, moduleNames));
+		return graph;
+	});
+}
+
+/**
+ * @param {ResourcePool} pool Modulepool to retrieve module information from
+ * @param {string[]} moduleNames list of modules to be sorted
+ * @returns {Promise} sorted list of modules
+ * @private
+ */
+function topologicalSort(pool, moduleNames) {
+	return createDependencyGraph(pool, moduleNames, false).
+		then(function(graph) {
+		// now do a topological sort.
+			const sequence = [];
+			let i;
+			let j;
+			let l = moduleNames.length;
+			moduleNames = moduleNames.slice(); // clone
+
+			do {
+				// invariant: the first 'l' items in moduleNames are still to be processed
+
+				// first loop over all remaining modules and emit those that don't have any more dependencies
+				for (i = 0, j = 0; i < l; i++ ) {
+					const moduleName = moduleNames[i];
+					const node = graph[moduleName];
+
+					// modules that don't have any unsatisfied dependencies can be emitted
+					if ( node == null || node.outgoing.length === 0 ) {
+						// console.log("emitting %s", moduleName, node);
+
+						// add module to sequence
+						sequence.push(moduleName);
+
+						// remove outgoing dependency to current module from all modules that depend on it
+						if ( node != null ) {
+							node.incoming.forEach( function(dependent) {
+								const index = dependent.outgoing.indexOf(node);
+								if ( index >= 0 ) {
+									dependent.outgoing.splice(index, 1);
+								// console.log("removing outgoing %s in %s", node.name, dependent.name);
+								} else {
+									log.error(`**** Could not find node ${node.name} in ${dependent.name}`);
+								}
+							});
+						}
+					} else {
+						moduleNames[j++] = moduleName;
+					}
+				}
+
+				// invariant: 'j' is the number of remaining items, 'i' is the same as 'l' now
+
+				l = j;
+
+			/* NODE-TODO metadata for cycle resolution not available yet
+			// if we have not been able to find a suitable module then we try to resolve well known cycles
+			if ( i === l && l > 0 cycles.hasNext() ) {
+
+				// get one cycle
+				Collection cycle = cycles.next();
+				console.debug("trying to resolve cycle %s", cycle);
+
+				// check that the full cycle is part of the remaining graph
+				for(ModuleName moduleName : cycle) {
+					if ( !moduleNames.contains(moduleName) ) {
+						throw new IllegalStateException("Misconfigured cycle, cannot resolve.");
+					}
+					trace.debug("	%s:%d", moduleName, graph.get(moduleName).outgoing.size());
+				}
+
+				for(ModuleName moduleName : cycle) {
+					GraphNode node = graph.get(moduleName);
+					// Note: we assume that members of a cycle always are described in the module pool,
+					// so we don't check for null
+
+					// remove all cycle members from the outgoing dependencies
+					for(ModuleName dep : cycle) {
+						node.outgoing.remove(graph.get(dep));
+					}
+
+					// if the module is eligible now, add it to the sequence
+					if ( node.outgoing.isEmpty() ) {
+						sequence.add(moduleName);
+						moduleNames.remove(moduleName);
+						modified = true;
+						for (GraphNode dependent : node.incoming) {
+							dependent.outgoing.remove(node);
+						}
+					}
+				}
+
+			} */
+			} while ( l && l < i ); // continue as long as there are remaining items and as long as we made progress
+
+			if ( l > 0 ) {
+				log.verbose(
+					Object.keys(graph)
+						.filter((name) => moduleNames.indexOf(name) < l)
+						.map((name) => graph[name].toString()));
+				throw new Error("Failed to resolve cyclic dependencies: " + moduleNames.slice(0, l).join(", ") );
+				// NODE-TODO use new CycleFinder(graph).toString() for easier to understand output
+			}
+
+			// console.log("module sequence: %s", sequence);
+			return sequence;
+		});
+}
+
+export default topologicalSort;
diff --git a/packages/builder/lib/lbt/resources/LibraryFileAnalyzer.js b/packages/builder/lib/lbt/resources/LibraryFileAnalyzer.js
new file mode 100644
index 00000000000..9345caecf15
--- /dev/null
+++ b/packages/builder/lib/lbt/resources/LibraryFileAnalyzer.js
@@ -0,0 +1,78 @@
+/**
+ * Used by the ResourcePool to read raw module info as stored in the .library files.
+ * It does not (yet) analyze dependencies of the library and therefore isn't part of the analyzer package (yet).
+ */
+import xml2js from "xml2js";
+import {getLogger} from "@ui5/logger";
+const log = getLogger("lbt:resources:LibraryFileAnalyzer");
+
+const parser = new xml2js.Parser({
+	// explicitChildren: true,
+	preserveChildrenOrder: true,
+	xmlns: true
+});
+
+/*
+ * Helper to simplify access to node attributes.
+ */
+function getAttribute(node, attr) {
+	return (node.$ && node.$[attr] && node.$[attr].value) || null;
+}
+
+/*
+ * Analyzes the given XML2JS object `rawModule` and creates a rawInfo object from it.
+ * @param {object} rawModule XML2JS object, representing a <raw-module> node from a .library file
+ * @returns {{name:string,dependencies?:string[],requiresTopLevelScope?:boolean,ignoredGlobals?:string[]}
+ */
+function createRawInfo(rawModule) {
+	const name = getAttribute(rawModule, "name");
+	if ( name ) {
+		const rawInfo = {
+			name,
+			rawModule: true,
+			dependencies: []
+		};
+		const deps = getAttribute(rawModule, "depends");
+		if ( deps != null ) {
+			rawInfo.dependencies = deps.trim().split(/\s*,\s*/);
+		}
+		const requiresTopLevelScope = getAttribute(rawModule, "requiresTopLevelScope");
+		if ( requiresTopLevelScope ) {
+			rawInfo.requiresTopLevelScope = requiresTopLevelScope === "true";
+		}
+		const ignoredGlobals = getAttribute(rawModule, "ignoredGlobals");
+		if ( ignoredGlobals ) {
+			rawInfo.ignoredGlobals = ignoredGlobals.trim().split(/\s*,\s*/);
+		}
+		return rawInfo;
+	}
+}
+
+export function getDependencyInfos( name, content ) {
+	const infos = Object.create(null);
+	parser.parseString(content, (err, result) => {
+		if ( result &&
+				result.library &&
+				Array.isArray(result.library.appData) &&
+				result.library.appData.length >= 1 &&
+				Array.isArray(result.library.appData[0].packaging) ) {
+			result.library.appData[0].packaging.forEach( (packaging) => {
+				if ( packaging.$ns &&
+						packaging.$ns.uri === "http://www.sap.com/ui5/buildext/packaging" &&
+						Array.isArray(packaging["module-infos"]) ) {
+					packaging["module-infos"].forEach( function(moduleInfos) {
+						moduleInfos["raw-module"] && moduleInfos["raw-module"].forEach( (rawModule) => {
+							const rawInfo = createRawInfo(rawModule);
+							if ( rawInfo ) {
+								log.verbose(name + " rawInfo: " + JSON.stringify(rawInfo));
+								infos[rawInfo.name] = rawInfo;
+							}
+						});
+					});
+				}
+			});
+		}
+	});
+	// console.log("done", infos);
+	return infos;
+}
diff --git a/packages/builder/lib/lbt/resources/LocatorResource.js b/packages/builder/lib/lbt/resources/LocatorResource.js
new file mode 100644
index 00000000000..682635bbc73
--- /dev/null
+++ b/packages/builder/lib/lbt/resources/LocatorResource.js
@@ -0,0 +1,22 @@
+import Resource from "./Resource.js";
+
+class LocatorResource extends Resource {
+	constructor(pool, resource, moduleName) {
+		super(pool, moduleName, null, resource.getStatInfo());
+		this.resource = resource;
+	}
+
+	buffer() {
+		return this.resource.getBuffer();
+	}
+
+	getProject() {
+		return this.resource.getProject();
+	}
+
+	getPath() {
+		return this.resource.getPath();
+	}
+}
+
+export default LocatorResource;
diff --git a/packages/builder/lib/lbt/resources/LocatorResourcePool.js b/packages/builder/lib/lbt/resources/LocatorResourcePool.js
new file mode 100644
index 00000000000..c2f0cd95770
--- /dev/null
+++ b/packages/builder/lib/lbt/resources/LocatorResourcePool.js
@@ -0,0 +1,19 @@
+import ResourcePool from "./ResourcePool.js";
+import LocatorResource from "./LocatorResource.js";
+
+class LocatorResourcePool extends ResourcePool {
+	prepare(resources, moduleNameMapping) {
+		resources = resources.filter( (res) => !res.getStatInfo().isDirectory() );
+		return Promise.all(
+			resources.map((resource) => {
+				let moduleName = moduleNameMapping && moduleNameMapping[resource.getPath()];
+				if (!moduleName) {
+					moduleName = resource.getPath().slice("/resources/".length);
+				}
+				return this.addResource(new LocatorResource(this, resource, moduleName));
+			}).filter(Boolean)
+		);
+	}
+}
+
+export default LocatorResourcePool;
diff --git a/packages/builder/lib/lbt/resources/ModuleInfo.js b/packages/builder/lib/lbt/resources/ModuleInfo.js
new file mode 100644
index 00000000000..aadd1e5b1fa
--- /dev/null
+++ b/packages/builder/lib/lbt/resources/ModuleInfo.js
@@ -0,0 +1,252 @@
+
+/**
+ * A strict dependency always has to be fulfilled and is declared as part of the module's definition.
+ *
+ * @private
+ */
+const STRICT = 0;
+
+/**
+ * An implicit dependency is also strict, but has not been declared. E.g. each UI5 module depends on
+ * jquery.sap.global.
+ *
+ * @private
+ */
+const IMPLICIT = 1;
+
+/**
+ * A conditional dependency has to be resolved only under certain conditions that typically are
+ * checked at runtime.
+ *
+ * @private
+ */
+const CONDITIONAL = 2;
+
+const Format = {
+	UI5_LEGACY: "ui5-declare",
+	UI5_DEFINE: "ui5-define",
+	AMD: "amd"
+};
+
+/**
+ * Information about an existing Module and its dependencies.
+ *
+ * @author Frank Weigel
+ * @since 1.1.2
+ * @private
+ */
+class ModuleInfo {
+	constructor(name) {
+		this._name = name;
+		this.subModules = [];
+		this._dependencies = Object.create(null);
+		this.dynamicDependencies = false;
+
+		/**
+		 * Description of the module
+		 */
+		this.description = undefined;
+
+		/**
+		 * Module format used by this module.
+		 *
+		 * @type {Format}
+		 */
+		this.format = undefined;
+
+		/**
+		 * 'raw' modules are modules that don't use UI5's module system (require/declare)
+		 * TODO align with module format (ui5, amd, es6, raw)
+		 *
+		 * A raw module is a module which does not have in its non-conditional execution:
+		 * 
    + *
  • sap.ui.define call
  • + *
  • jQuery.sap.declare call
  • + *
+ */ + this.rawModule = false; + + /** + * Whether the module requires top level scope (true) or whether it can be embedded + * in another function scope (e.g. for preload). + * + * Even when a module declares top level variables, it might be possible to embed it + * when all of the following criteria are met: + *
    + *
  • all relevant global variables are set during the execution of the module + *
  • all relevant global variables are treated like "const", e.g. they are only + * set once and not modified afterwards + *
+ * + * The module analyzer doesn't recognize whether these criteria are met. + * Instead, developers can provide this information in the raw-modules section of + * the corresponding .library file. By default, any modules is assumed NOT to require + * global scope. If a module declares global variables, all of them will be exported + * with additional code of the form: + * + *
+		 *	 this["name"] = name;
+		 * 
+ * + * To avoid this, developers can either add "name" to the ignoreGlobals + * attribute of a raw-module or they can completely suppress embedding of the module + * by setting the requiresTopLevelScope attribute to true. + * + * @returns Whether the module requires top level scope. + */ + this.requiresTopLevelScope = false; + + /** + * Global names that the module exposes intentionally and that should be exported + * when the module is wrapped in another scope (e.g. for function preload). + * + * Implementation Note: during module analysis, this collections contains all global names. + * Based on external metadata (shims), ignorable names are removed from the collection. + */ + this.exposedGlobals = undefined; + } + + _addDependency(dependency, kind) { + // add the dependency only when it is defined, not empty, + // not equal to the current module itself and if the same module is not + // included already as a submodule. + // If the dependency was known already, update the kind + // only when the new kind is stronger than the current one. + // STRICT is stronger than IMPLICIT, IMPLICIT is stronger than CONDITIONAL + if ( dependency && + dependency !== this.name && + this.subModules.indexOf(dependency) < 0 && + ( !(dependency in this._dependencies) || kind < this._dependencies[dependency]) ) { + this._dependencies[dependency] = kind; + } + } + + addImplicitDependency(dependency) { + this._addDependency(dependency, IMPLICIT); + } + + addDependency(dependency, conditional) { + this._addDependency(dependency, conditional ? CONDITIONAL : STRICT); + } + + setFormat(detectedFormat) { + if ( this.format == null || + detectedFormat === Format.UI5_LEGACY || + (detectedFormat === Format.UI5_DEFINE && this.format !== Format.UI5_LEGACY) ) { + this.format = detectedFormat; + } + } + + /** + * Adds the given module as a sub module to this module. + * + * If the module is an instanceof ModuleInfo, its name is added to submodules, + * its dependencies become dependencies of this module (if they are not already + * included as submodules). If the included module has calculated (dynamic) + * dependencies, then this module inherits this 'quality'. + * + * Other data (like size, top level vars etc.) is not aggregated here as + * the correct aggregation highly depends on the way how the module is merged + * (preload, legacy embedding, raw module, ...) + * + * If other is a string, it is simply added to subModules. + * + * @param {string | ModuleInfo} other Module to include into this module + */ + addSubModule( other ) { + if ( other instanceof ModuleInfo ) { + this.addSubModule( other.name ); + // accumulate dependencies + for ( const dep of Object.keys(other._dependencies ) ) { + this._addDependency(dep, other._dependencies[dep]); + } + // inherit dynamic dependencies + if ( other.dynamicDependencies ) { + this.dynamicDependencies = true; + } + } else { + this.subModules.push( other ); + // when a module is added as submodule, it no longer is a dependency + delete this._dependencies[other]; + } + } + + isConditionalDependency(dependency) { + return this._dependencies[dependency] === CONDITIONAL; + } + + isImplicitDependency(dependency) { + return this._dependencies[dependency] === IMPLICIT; + } + + get name() { + return this._name; + } + + set name(n) { + this._name = n; + if ( n != null ) { + if ( Object.prototype.hasOwnProperty.call(this._dependencies, n) ) { + delete this._dependencies[n]; + } + + const idx = this.subModules.indexOf(n); + if ( idx >= 0 ) { + this.subModules.splice(idx, 1); + } + } + } + + get dependencies() { + return Object.keys(this._dependencies); + } + + /** + * Removes the given set of `ignoredGlobals` from the set of exposed global names. + * + * @param {string[]} ignoredGlobals Names to ignore (determined from shims in .library) + */ + removeIgnoredGlobalNames(ignoredGlobals) { + if ( this.exposedGlobals ) { + const remaining = this.exposedGlobals.filter((global) => !ignoredGlobals.includes(global)); + this.exposedGlobals = remaining.length > 0 ? remaining : null; + } + } + + toString() { + return "ModuleInfo(" + + this.name + + ", dependencies=" + this.dependencies + + ", includes=" + this.subModules + + ")"; + } +} + +// expose format enum +ModuleInfo.Format = Format; + +/* NODE-TODO +public class ModuleInfo { + + private String resourcePath; + + private String description; + + private File file; + + private long size; + + private long compressedSize = -1; + + private boolean rawModule; + + /** + * Whether the module must not run automatically (e.g. because it is to be used only in certain contexts) + * + * This is relevant during the generation of the all-in-one files. + * + private boolean excludeFromAllInOne; + +} */ + +export default ModuleInfo; diff --git a/packages/builder/lib/lbt/resources/Resource.js b/packages/builder/lib/lbt/resources/Resource.js new file mode 100644 index 00000000000..29b7a6c76d8 --- /dev/null +++ b/packages/builder/lib/lbt/resources/Resource.js @@ -0,0 +1,29 @@ + +import {promisify} from "node:util"; +import fs from "graceful-fs"; +const readFile = promisify(fs.readFile); + +class Resource { + constructor(pool, name, file, stat) { + this.pool = pool; + this.name = name; + this.file = file; + this.fileSize = stat ? stat.size : -1; + } + + /** + * @returns {Promise} Buffer of file + */ + async buffer() { + return readFile(this.file); + } + + /** + * @returns {Promise} String of the file content + */ + async string() { + return (await this.buffer()).toString(); + } +} + +export default Resource; diff --git a/packages/builder/lib/lbt/resources/ResourceCollector.js b/packages/builder/lib/lbt/resources/ResourceCollector.js new file mode 100644 index 00000000000..927ce2dd1b5 --- /dev/null +++ b/packages/builder/lib/lbt/resources/ResourceCollector.js @@ -0,0 +1,402 @@ +import ResourceInfoList from "./ResourceInfoList.js"; +import ResourceFilterList from "./ResourceFilterList.js"; +import ResourceInfo from "./ResourceInfo.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:resources:ResourceCollector"); + +const LOCALE = /^((?:[^/]+\/)*[^/]+?)_([A-Z]{2}(?:_[A-Z]{2}(?:_[A-Z0-9_]+)?)?)(\.properties|\.hdbtextbundle)$/i; +const THEME = /^((?:[^/]+\/)*)themes\/([^/]+)\//; + +/** + * Collects all resources in a set of resource folders or from an archive + * and writes a 'resources.json' file for each component or library + * that can be found in the resources. + * + * @since 1.29.0 + */ +class ResourceCollector { + /** + * Collects a set of ResourceInfo objects and groups them by components, libraries and themes. + * + * @param {ResourcePool} pool + * @param {ResourceFilterList} [filter] used to filter the resources based on their name + */ + constructor(pool, filter) { + this._pool = pool; + /** + * Global filters that should be taken into account when collecting resources. + * + * @type {ResourceFilterList} + * @private + */ + this._filter = filter != null ? filter : new ResourceFilterList(); + /** + * name to resource info + * + * @type {Map} + */ + this._resources = new Map(); + + /** + * prefix to ResourceInfoList + * + * @type {Map} + * @private + */ + this._components = new Map(); + + /** + * prefix to ResourceInfoList + * + * @type {Map} + * @private + */ + this._themePackages = new Map(); + } + + /** + * Comma separated list of components to which orphaned resources should be added. + * + * A component and a separated list of resource patterns of orphans that should be added + * to the preceding component. + * If no such list is given, any orphaned resource will be added to the component. + * The evaluation logic for the filter list is the same as for the filters + * parameters: excludes can be denoted with a leading '-' or '!' and order is significant. + * Later filters can override the result of earlier ones. + * + * If no component is given, orphans will only be reported but not added to any component (default). + * + * @param {Object} list component to list of components + */ + setExternalResources(list) { + this._externalResources = list; + } + + /** + * Processes a resource + * + * @param {@ui5/fs/Resource} resource + */ + async visitResource(resource) { + const virPath = resource.getPath(); + if ( !virPath.startsWith("/resources/") ) { + log.warn(`Non-runtime resource ${virPath} ignored`); + return; + } + const name = virPath.slice("/resources/".length); + if ( this._filter.matches(name) ) { + const resourceInfo = new ResourceInfo(name); + resourceInfo.size = await resource.getSize(); + this._resources.set(name, resourceInfo); + + const p = name.lastIndexOf("/"); + const prefix = name.substring(0, p + 1); + const basename = name.substring(p + 1); + if ( basename.match("^([^/]*\\.library|Component\\.js|manifest\\.json)$") && + !this._components.has(prefix)) { + this._components.set(prefix, new ResourceInfoList(prefix)); + } + // a .theme file within a theme folder indicates a library/theme package + // Note: ignores .theme files in library folders + + // .theming files are not always present therefore this check is relevant for the library.source.less + if ( name.match("(?:[^/]+/)*themes/[^/]+/(?:\\.theming|library\\.source\\.less)") && + !this._themePackages.has(prefix) ) { + // log.info("Found new theme package %s", prefix); + this._themePackages.set(prefix, new ResourceInfoList(prefix)); + } + } + } + + async enrichWithDependencyInfo(resourceInfo) { + return this._pool.getModuleInfo(resourceInfo.name, resourceInfo.module).then(async (moduleInfo) => { + if ( !resourceInfo.module && moduleInfo.name ) { + resourceInfo.module = moduleInfo.name; + } + + if ( moduleInfo.dynamicDependencies ) { + resourceInfo.dynRequired = true; + } + + if ( moduleInfo.dependencies.length > 0 ) { + resourceInfo.required = resourceInfo.required || new Set(); + resourceInfo.condRequired = resourceInfo.condRequired || new Set(); + moduleInfo.dependencies.forEach((dep) => { + if ( moduleInfo.isConditionalDependency(dep) ) { + resourceInfo.condRequired.add(dep); + } else if ( !moduleInfo.isImplicitDependency(dep) ) { + resourceInfo.required.add(dep); + } + }); + } + + if ( moduleInfo.subModules.length > 0 ) { + resourceInfo.included = resourceInfo.included || new Set(); + moduleInfo.subModules.forEach((mod) => { + resourceInfo.included.add(mod); + }); + await Promise.all(moduleInfo.subModules.map(async (subModule) => { + // Try to inherit dependency info + let subModuleInfo; + try { + subModuleInfo = await this._pool.getModuleInfo(subModule); + } catch { + log.verbose(` Missing submodule ${subModule} included by ${moduleInfo.name}`); + } + if (subModuleInfo) { + // Inherit subModule dependencies + if ( subModuleInfo.dependencies.length > 0 ) { + resourceInfo.required = resourceInfo.required || new Set(); + resourceInfo.condRequired = resourceInfo.condRequired || new Set(); + subModuleInfo.dependencies.forEach((dep) => { + if (resourceInfo.included.has(dep)) { + // Don't add dependency if module is already listed as "included" + return; + } + if ( subModuleInfo.isConditionalDependency(dep) ) { + // Avoid having module listed in both required and condRequired + if (!resourceInfo.required.has(dep)) { + resourceInfo.condRequired.add(dep); + } + } else if ( !subModuleInfo.isImplicitDependency(dep) ) { + // Move module from condRequired to required + if (resourceInfo.condRequired.has(dep)) { + resourceInfo.condRequired.delete(dep); + } + resourceInfo.required.add(dep); + } + }); + } + + // Inherit dynamicDependencies flag + if ( moduleInfo.dynamicDependencies ) { + resourceInfo.dynRequired = true; + } + } + })); + } + + if (moduleInfo.requiresTopLevelScope) { + resourceInfo.requiresTopLevelScope = true; + } + if (moduleInfo.exposedGlobals != null && moduleInfo.exposedGlobals.length) { + resourceInfo.exposedGlobalNames = resourceInfo.exposedGlobalNames || new Set(); + moduleInfo.exposedGlobals.forEach((exposedGlobalName) => { + resourceInfo.exposedGlobalNames.add(exposedGlobalName); + }); + } + + if (moduleInfo.rawModule) { + resourceInfo.format = "raw"; + } + }); + } + + async determineResourceDetails({ + debugResources, mergedResources, designtimeResources, supportResources + }) { + const baseNames = new Set(); + const debugFilter = new ResourceFilterList(debugResources); + const mergeFilter = new ResourceFilterList(mergedResources); + const designtimeFilter = new ResourceFilterList(designtimeResources); + const supportFilter = new ResourceFilterList(supportResources); + + const promises = []; + const debugResourcesInfo = []; + + for (const [name, info] of this._resources.entries()) { + if ( debugFilter.matches(name) ) { + info.isDebug = true; + log.verbose(` Found potential debug resource '${name}'`); + } + + // log.verbose(` checking ${name}`); + let m; + if ( m = LOCALE.exec(name) ) { + const baseName = m[1] + m[3]; + log.verbose(` Found potential i18n resource '${name}', base name is '${baseName}', locale is ${m[2]}`); + info.i18nName = baseName; // e.g. "i18n.properties" + info.i18nLocale = m[2]; // e.g. "de" + baseNames.add(baseName); + } + + if ( m = THEME.exec(name) ) { + // log.verbose("found theme candidate %s with prefix %s", name, m[1]); + if ( this._themePackages.has(m[0]) ) { + const theme = m[2]; + info.theme = theme; + log.verbose(` Found potential theme resource '${name}', theme ${theme}`); + } + } + + if ( /(?:\.js|\.view\.xml|\.control\.xml|\.fragment\.xml)$/.test(name) ) { + if ( !info.isDebug ) { + // Only analyze non-dbg files in first run + promises.push( + this.enrichWithDependencyInfo(info) + ); + } else { + // Collect dbg files to be handled in a second step (see below) + debugResourcesInfo.push(info); + } + } + + // set the module name for .properties and .json + if ( /(?:\.properties|\.json)$/.test(name) ) { + promises.push(new Promise((resolve) => { + return this._pool.getModuleInfo(info.name).then((moduleInfo) => { + if (moduleInfo.name) { + info.module = moduleInfo.name; + } + resolve(); + }); + })); + } + + if ( mergeFilter.matches(name) ) { + info.merged = true; + log.verbose(` Found potential merged resource '${name}'`); + } + + if ( designtimeFilter.matches(name) ) { + info.designtime = true; + log.verbose(` Found potential designtime resource '${name}'`); + } + + if ( supportFilter.matches(name) ) { + info.support = true; + log.verbose(` Found potential support resource '${name}'`); + } + } + + for (const baseName of baseNames) { + if ( this._resources.has(baseName) ) { + const info = this._resources.get(baseName); + info.i18nName = baseName; + info.i18nLocale = ""; + } + } + + await Promise.all(promises); + + await Promise.all(debugResourcesInfo.map(async (dbgInfo) => { + const debugName = dbgInfo.name; + const nonDebugName = ResourceInfoList.getNonDebugName(debugName); + const nonDbgInfo = this._resources.get(nonDebugName); + + // FIXME: "merged" property is only calculated in ResourceInfo#copyFrom + // Therefore using the same logic here to compute it. + + // TODO: Idea: Use IsDebugVariant tag to decide whether to analyze the resource + // If the tag is set, we don't expect different analysis results so we can copy the info (else-path) + // Only when the tag is not set, we analyze the resource with its name (incl. -dbg) + + if (!nonDbgInfo || (nonDbgInfo.included != null && nonDbgInfo.included.size > 0)) { + // We need to analyze the dbg resource if there is no non-dbg variant or + // it is a bundle because we will (usually) have different content. + + if (nonDbgInfo) { + // Always use the non-debug module name, if available + dbgInfo.module = nonDbgInfo.module; + } + await this.enrichWithDependencyInfo(dbgInfo); + } else { + // If the non-dbg resource is not a bundle, we can just copy over the info and skip + // analyzing the dbg variant as both should have the same info. + + const newDbgInfo = new ResourceInfo(debugName); + + // First copy info of analysis from non-dbg file (included, required, condRequired, ...) + newDbgInfo.copyFrom(null, nonDbgInfo); + // Then copy over info from dbg file to properly set name, isDebug, etc. + newDbgInfo.copyFrom(null, dbgInfo); + // Finally, set the module name to the non-dbg name + newDbgInfo.module = nonDbgInfo.module; + + this._resources.set(debugName, newDbgInfo); + } + })); + } + + createOrphanFilters() { + log.verbose( + ` Configured external resources filters (resources outside the namespace): ` + + `${this._externalResources == null ? "(none)" : this._externalResources}`); + + const filtersByComponent = new Map(); + + if ( this._externalResources != null ) { + for ( let [component, filters] of Object.entries(this._externalResources) ) { + const packageFilters = new ResourceFilterList(filters); + if ( component === "/" || component === "" ) { + component = ""; + } else if ( !component.endsWith("/") ) { + component += "/"; + } + log.verbose(` Resulting filter list for '${component}': '${packageFilters}'`); + filtersByComponent.set(component, packageFilters); + } + } + return filtersByComponent; + } + + groupResourcesByComponents() { + const orphanFilters = this.createOrphanFilters(); + for (const resource of this._resources.values()) { + let contained = false; + for (const [prefix, list] of this._components.entries()) { + if ( resource.name.startsWith(prefix) ) { + list.add(resource); + contained = true; + } else if ( orphanFilters.has(prefix) ) { + // log.verbose(` checking '${resource.name}' against orphan filter ` + + // `'${orphanFilters.get(prefix)}' (${prefix})`); + if ( orphanFilters.get(prefix).matches(resource.name) ) { + list.add(resource); + contained = true; + } + } + } + + if ( resource.theme != null ) { + // assign theme resources additionally to theme packages + for (const [prefix, list] of this._themePackages.entries()) { + if ( resource.name.startsWith(prefix) ) { + list.add(resource); + contained = true; + } + } + } + + if ( contained ) { + this._resources.delete(resource.name); + } + } + } + + /** + * + * @returns {Set} resource names + */ + get resources() { + return new Set(this._resources.keys()); + } + + /** + * Components + * + * @returns {Map} + */ + get components() { + return this._components; + } + + /** + * @returns {Map} + */ + get themePackages() { + return this._themePackages; + } +} + +export default ResourceCollector; diff --git a/packages/builder/lib/lbt/resources/ResourceFilterList.js b/packages/builder/lib/lbt/resources/ResourceFilterList.js new file mode 100644 index 00000000000..bb110b1ff0c --- /dev/null +++ b/packages/builder/lib/lbt/resources/ResourceFilterList.js @@ -0,0 +1,184 @@ + +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:resources:ResourceFilterList"); + +const FILTER_PREFIXES = /^[-!+]/; + +function makeFileTypePattern(fileTypes) { + if ( fileTypes == null ) { + return undefined; + } + return "(?:" + fileTypes.map((type) => { + if ( !type.startsWith(".") ) { + type = "." + type; + } + return type.replace(/[*+?.()|^$\\]/g, "\\$&"); + }).join("|") + ")"; +} + +function makeMatcher(globPattern, fileTypesPattern) { + const result = { + pattern: globPattern, + include: true + }; + + // cut off leading '!', '-' or '+' + if ( FILTER_PREFIXES.test(globPattern) ) { + result.include = globPattern[0] === "+"; + globPattern = globPattern.slice(1); + } + + // normalize some convenience shortcuts + // - a lonely 'any sub-path' pattern implies the 'any file' pattern: + // "**/" --> "**/*" + // - a trailing 'any sub-path' pattern also implies the 'any file' pattern: + // ".../foo/**/" --> "../foo/**/*" + // - any other trailing slash matches any files in any sub-folder: + // ".../foo/" --> ".../foo/**/*" + if ( globPattern.endsWith("/") ) { + if ( globPattern === "**/" || globPattern.endsWith("/**/") ) { + globPattern = globPattern + "*"; + } else { + globPattern = globPattern + "**/*"; + } + } + + // check for wildcards + if ( /\*/.test(globPattern) ) { + // Transform the globPattern into a regular expression pattern by converting + // the "all sub-directories" pattern "/**/" and the "any file name" pattern "*" + // to their respective regexp counterparts and escape all other regexp special + // characters. + let regexp = globPattern.replace(/^\*\*\/|\/\*\*\/|\*|[[\]{}()+?.\\^$|]/g, (match) => { + switch (match) { + case "**/": return "(?:[^/]+/)*"; + case "/**/": return "/(?:[^/]+/)*"; + case "*": return "[^/]*"; + default: return "\\" + match; + } + }); + + // if the pattern ended with an asterisk and if a default file type pattern is defined, + // add that pattern. This limits the matches to the specified set of file types + if ( fileTypesPattern != null && regexp.endsWith("[^/]*") ) { + regexp = regexp + fileTypesPattern; + } + + result.regexp = new RegExp("^" + regexp + "$"); + result.calcMatch = result.include ? function(candidate, matchSoFar) { + return matchSoFar || this.regexp.test(candidate); + } : function(candidate, matchSoFar) { + return matchSoFar && !this.regexp.test(candidate); + }; + + log.verbose(` ${result.pattern} --> ${result.include ? "include" : "exclude"}: /${result.regexp.source}/`); + } else { + result.value = globPattern; + log.verbose(` ${result.pattern} --> ${result.include ? "include" : "exclude"}: "${globPattern}"`); + result.calcMatch = result.include ? function(candidate, matchSoFar) { + return matchSoFar || candidate === this.value; + } : function(candidate, matchSoFar) { + return matchSoFar && candidate !== this.value; + }; + } + + return result; +} + +/** + * Helper class to manage multiple resource name filters. + * + * Each filter can be flagged as include or exclude. + * Order of the filters is significant. + * + * @author Frank Weigel + * @since 1.16.2 + * @private + */ +export default class ResourceFilterList { + constructor(filters, fileTypes) { + this.matchers = []; + this.matchByDefault = true; + this.fileTypes = makeFileTypePattern(fileTypes); + log.verbose(`Filetypes: ${fileTypes}`); + this.addFilters(filters); + } + + addFilters(filters) { + if ( Array.isArray(filters) ) { + filters.forEach( (filter) => { + const matcher = makeMatcher(filter, this.fileTypes); + this.matchers.push( matcher ); + this.matchByDefault = this.matchByDefault && !matcher.include; + }); + } else if ( filters != null ) { + throw new Error("unsupported filter " + filters); + } + return this; + } + + matches(candidate, initialMatch) { + return this.matchers.reduce( + (acc, cur) => cur.calcMatch(candidate, acc), + initialMatch == null ? this.matchByDefault : initialMatch + ); + } + + toString() { + return this.matchers.map((matcher) => matcher.pattern).join(","); + } + + /** + * Each filter entry can be a comma separated list of simple filters. Each simple filter + * can be a pattern in resource name pattern syntax: A double asterisk '&0x2a;&0x2a;/' denotes an arbitrary + * number of resource name segments (folders) incl. a trailing slash, whereas a simple asterisk '*' + * denotes an arbitrary number of resource name characters, but not the segment separator '/'. + * A dot is interpreted as a dot, all other special regular expression characters keep their + * special meaning. This is a mixture of ANT-style path patterns and regular expressions. + * + * Excludes can be denoted by a leading '-' or '!', includes optionally by a leading '+'. + * Order of filters is significant, a later exclusion overrides an earlier inclusion + * and vice versa. + * + * Example: + *
+	 *	 !sap/ui/core/
+	*	 +sap/ui/core/utils/
+	* 
+ * excludes everything from sap/ui/core, but includes everything from the subpackage sap/ui/core/utils/. + * + * Note that the filter operates on the full name of each resource. If a resource name + * prefix is configured for a resource set, the filter will be applied + * to the combination of prefix and local file path and not only to the local file path. + * + * @param {string} filterStr comma separated list of simple filters + * @returns {ResourceFilterList} + */ + static fromString(filterStr) { + const result = new ResourceFilterList(); + if ( filterStr != null ) { + result.addFilters( filterStr.trim().split(/\s*,\s*/).filter(Boolean) ); + } + return result; + } +} + +export function negateFilters(patterns) { + return patterns.map((pattern) => { + let include = true; + + // cut off leading '!', '-' or '+' + if (FILTER_PREFIXES.test(pattern)) { + include = pattern[0] === "+"; + pattern = pattern.slice(1); + } + + if (include) { + // include => exclude + return "!" + pattern; + } else { + // exclude => include + return "+" + pattern; + } + }); +} diff --git a/packages/builder/lib/lbt/resources/ResourceInfo.js b/packages/builder/lib/lbt/resources/ResourceInfo.js new file mode 100644 index 00000000000..1e3ee063ab7 --- /dev/null +++ b/packages/builder/lib/lbt/resources/ResourceInfo.js @@ -0,0 +1,180 @@ +/** + * Information about a single resource as stored in the resources.json file. + * + * @author Frank Weigel + * @since 1.33.0 + */ +class ResourceInfo { + /** + * @param {string} name name of the resource + */ + constructor(name) { + this.name = name; + this.i18nName = null; + this.i18nLocale = null; + this.isDebug = false; + this.theme = null; + this.merged = false; + this.designtime = false; + this.support = false; + this._module = null; + this.required = null; + this.condRequired = null; + this.included = null; + this.dynRequired = false; + this.requiresTopLevelScope = false; + this.exposedGlobalNames = null; + this._format = null; + this._size = -1; + } + + + get module() { + return this._module; + } + + set module(value) { + this._module = value; + } + + get format() { + return this._format; + } + + set format(value) { + this._format = value; + } + + get size() { + return this._size; + } + + set size(value) { + this._size = value; + } + + /** + * Copies the properties of the given ResourceInfo into this + * + * @param {string} prefix + * @param {ResourceInfo} orig + */ + copyFrom(prefix, orig) { + this.i18nName = orig.i18nName; + this.i18nLocale = orig.i18nLocale; + this.isDebug = orig.isDebug; + this.theme = orig.theme; + this.merged = orig.merged; + this.designtime = orig.designtime; + this.support = orig.support; + if ( this.module == null ) { + this.module = orig.module; + } + if ( orig.required != null ) { + if ( this.required == null ) { + this.required = new Set(); + } + orig.required.forEach(this.required.add, this.required); + } + if ( orig.condRequired != null ) { + if ( this.condRequired == null ) { + this.condRequired = new Set(); + } + orig.condRequired.forEach(this.condRequired.add, this.condRequired); + } + if ( orig.dynRequired ) { + this.dynRequired = orig.dynRequired; + } + if ( orig.included != null ) { + if ( this.included == null ) { + this.included = new Set(); + } + orig.included.forEach(this.included.add, this.included); + } + if ( this.included != null && this.included.size > 0 ) { + this.merged = true; + } + if (orig.size >= 0) { + this.size = orig.size; + } + if ( orig.requiresTopLevelScope ) { + this.requiresTopLevelScope = orig.requiresTopLevelScope; + } + if ( orig.exposedGlobalNames != null ) { + if ( this.exposedGlobalNames == null ) { + this.exposedGlobalNames = new Set(); + } + orig.exposedGlobalNames.forEach(this.exposedGlobalNames.add, this.exposedGlobalNames); + } + if ( orig.format != null ) { + this.format = orig.format; + } + } + + /** + * called from JSON.stringify() + * + * @returns {{name: *}} + */ + toJSON() { + const result = { + name: this.name + }; + if ( this._module != null ) { + result.module = this.module; + } + if ( this.size >= 0 ) { + result.size = this.size; + } + if ( this.requiresTopLevelScope ) { + result.requiresTopLevelScope = this.requiresTopLevelScope; + } + if ( this.exposedGlobalNames != null && this.exposedGlobalNames.size > 0 ) { + result.exposedGlobalNames = [...this.exposedGlobalNames]; + } + if ( this.format ) { + result.format = this.format; + } + + // + + if ( this.isDebug ) { + result.isDebug = this.isDebug; + } + if ( this.merged ) { + result.merged = this.merged; + } + if ( this.designtime ) { + result.designtime = this.designtime; + } + if ( this.support ) { + result.support = this.support; + } + if ( this.i18nLocale != null ) { + result.locale = this.i18nLocale; + result.raw = this.i18nName; + } + if ( this.theme != null ) { + result.theme = this.theme; + } + + // + + if ( this.required != null && this.required.size > 0 ) { + result.required = [...this.required].sort(); + } + if ( this.condRequired != null && this.condRequired.size > 0 ) { + result.condRequired = [...this.condRequired].sort(); + } + if ( this.dynRequired ) { + result.dynRequired = this.dynRequired; + } + if ( this.included != null && this.included.size > 0 ) { + result.included = [...this.included]; + } + + return result; + } +} + +export default ResourceInfo; diff --git a/packages/builder/lib/lbt/resources/ResourceInfoList.js b/packages/builder/lib/lbt/resources/ResourceInfoList.js new file mode 100644 index 00000000000..fbe1de78be1 --- /dev/null +++ b/packages/builder/lib/lbt/resources/ResourceInfoList.js @@ -0,0 +1,119 @@ +import ResourceInfo from "./ResourceInfo.js"; + +const DEBUG_RESOURCES_PATTERN = /-dbg((?:\.view|\.fragment|\.controller|\.designtime|\.support)?\.js|\.css)$/; + +/** + * A list of ResourceInfo objects, suitable for (but not dependent on) JSON serialization. + * + * @author Frank Weigel + * @since 1.33.0 + */ +class ResourceInfoList { + /** + * Holds ResourceInfos + * + * @param {string} prefix + */ + constructor(prefix) { + /** + * List of resources information objects + * + * @type {ResourceInfo[]} + */ + this.resources = []; + + // --- transient state --- + /** + * The name of the resource + * + * @type {string} + */ + this.name = prefix; + /** + * + * @type {Map} + */ + this.resourcesByName = new Map(); + } + + /** + * Add ResourceInfo to list + * + * @param {ResourceInfo} info + */ + add(info) { + const relativeName = ResourceInfoList.makePathRelativeTo(this.name, info.name); + // search for a resource with the same name + let myInfo = this.resourcesByName.get(relativeName); + + // this is the assumption, that the debug one is the same as the non-dbg one + if ( myInfo == null ) { + myInfo = new ResourceInfo(relativeName); + myInfo.size = info.size; + this.resources.push(myInfo); + this.resourcesByName.set(relativeName, myInfo); + } + myInfo.copyFrom(this.name, info); + if (info.i18nName) { + myInfo.i18nName = ResourceInfoList.makePathRelativeTo(this.name, info.i18nName); + } + } + + /** + * Serializes its content to JSON format + * + * @returns {{resources: ResourceInfo[], _version: string}} + */ + toJSON() { + this.resources.sort((a, b) => { + if ( a.name === b.name ) { + return 0; + } + return a.name < b.name ? -1 : 1; + }); + return { + /** + * Version of the resources.json file format, must be 1.1.0 or higher to store dependencies + */ + _version: "1.1.0", + resources: this.resources + }; + } + + /** + * Retrieves the relative path + * + * @param {string} prefix + * @param {string} name + * @returns {string} + */ + static makePathRelativeTo(prefix, name) { + let back = ""; + while ( !name.startsWith(prefix) ) { + const p = prefix.lastIndexOf("/", prefix.length - 2); + back = back + "../"; + if ( p >= 0 ) { + prefix = prefix.slice(0, p + 1); + } else { + prefix = ""; + break; + } + } + return back + name.slice(prefix.length); + } + + /** + * If the given module is a -dbg file, calculate and return the non-dbg name. + * + * @param {string} path + * @returns {string|null} Non-debug name of the module + */ + static getNonDebugName(path) { + if ( DEBUG_RESOURCES_PATTERN.test(path) ) { + return path.replace( DEBUG_RESOURCES_PATTERN, "$1"); + } + return null; + } +} + +export default ResourceInfoList; diff --git a/packages/builder/lib/lbt/resources/ResourcePool.js b/packages/builder/lib/lbt/resources/ResourcePool.js new file mode 100644 index 00000000000..ae97746427c --- /dev/null +++ b/packages/builder/lib/lbt/resources/ResourcePool.js @@ -0,0 +1,246 @@ +import {parseJS} from "../utils/parseUtils.js"; +import ComponentAnalyzer from "../analyzer/ComponentAnalyzer.js"; +import SmartTemplateAnalyzer from "../analyzer/SmartTemplateAnalyzer.js"; +import XMLCompositeAnalyzer from "../analyzer/XMLCompositeAnalyzer.js"; +import JSModuleAnalyzer from "../analyzer/JSModuleAnalyzer.js"; +import XMLTemplateAnalyzer from "../analyzer/XMLTemplateAnalyzer.js"; +import {getDependencyInfos} from "./LibraryFileAnalyzer.js"; +import ModuleInfo from "./ModuleInfo.js"; +import ResourceFilterList from "./ResourceFilterList.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("lbt:resources:ResourcePool"); +/* +import Resource from "./Resource"; + */ + +const jsAnalyzer = new JSModuleAnalyzer(); + +/* +function scanFileOrDir(fileOrDir, name, pool) { + // console.log("visiting " + fileOrDir + " (" + name + ")"); + return new Promise( (resolve, reject) => { + fs.stat(fileOrDir, (statErr, stat) => { + if ( statErr ) { + resolve(statErr); + } else if ( stat.isDirectory() ) { + if ( name && name.slice(-1) !== "/" ) { + name = name + "/"; + } + fs.readdir(fileOrDir, (dirErr, files) => { + if ( dirErr ) { + reject(dirErr); + } else { + resolve( + Promise.all( + files.map( (file) => scanFileOrDir(path.join(fileOrDir, file), name + file, pool) ) + ) + ); + } + }); + } else if ( /\.(?:js|json|xml|html|properties|library)$/.test(name) ) { + // TODO think about right place for configuration of such a global filtering + const resource = new Resource(pool, name, fileOrDir, stat); + resolve( pool.addResource( resource ) ); + } else { + // else: ignore other resource types + resolve(true); + } + }); + }); +} +*/ + +async function determineDependencyInfo(resource, rawInfo, pool) { + const info = new ModuleInfo(resource.name); + info.size = resource.fileSize; + if ( /\.js$/.test(resource.name) ) { + // console.log("analyzing %s", resource.file); + const code = await resource.buffer(); + info.size = code.length; + const promises = []; + let ast; + try { + ast = parseJS(code, {comment: true}); + } catch (err) { + log.error(`Failed to parse ${resource.name}: ${err.message}`); + log.verbose(err.stack); + } + if (ast) { + try { + jsAnalyzer.analyze(ast, resource.name, info); + new XMLCompositeAnalyzer(pool).analyze(ast, resource.name, info); + } catch (error) { + log.error(`Failed to analyze ${resource.name}: ${error.message}`); + log.verbose(error.stack); + } + } + if ( rawInfo ) { + info.rawModule = true; + // console.log("adding preconfigured dependencies for %s:", resource.name, rawInfo.dependencies); + if ( rawInfo.dependencies ) { + rawInfo.dependencies.forEach( (dep) => info.addDependency(dep) ); + } + + if ( rawInfo.requiresTopLevelScope != null ) { + // an explicitly defined value for requiresTopLevelScope from .library overrides analysis result + info.requiresTopLevelScope = rawInfo.requiresTopLevelScope; + } + + if ( rawInfo.ignoredGlobals ) { + info.removeIgnoredGlobalNames(rawInfo.ignoredGlobals); + } + } + if ( /(?:^|\/)Component\.js/.test(resource.name) ) { + promises.push( + new ComponentAnalyzer(pool).analyze(resource, info), + new SmartTemplateAnalyzer(pool).analyze(resource, info) + ); + } else if ( /(?:^|\/)library\.js/.test(resource.name) ) { + promises.push( + (async () => { + const manifestName = resource.name.replace(/library\.js$/, "manifest.json"); + const manifestResource = await pool.findResource(manifestName).catch(() => null); + if (manifestResource) { + info.addDependency(manifestName); + } + })() + ); + } + + await Promise.all(promises); + + // console.log(info); + } else if ( /\.view.xml$/.test(resource.name) ) { + const xmlView = await resource.buffer(); + info.size = xmlView.length; + await new XMLTemplateAnalyzer(pool).analyzeView(xmlView, info); + } else if ( /\.control.xml$/.test(resource.name) ) { + const xmlView = await resource.buffer(); + info.size = xmlView.length; + await new XMLTemplateAnalyzer(pool).analyzeFragment(xmlView, info); + } else if ( /\.fragment.xml$/.test(resource.name) ) { + const xmlView = await resource.buffer(); + info.size = xmlView.length; + await new XMLTemplateAnalyzer(pool).analyzeFragment(xmlView, info); + } + + return info; +} + +class ResourcePool { + constructor({ignoreMissingModules} = {}) { + this._ignoreMissingModules = !!ignoreMissingModules; + // this._roots = []; + this._resources = []; + this._resourcesByName = new Map(); + this._dependencyInfos = new Map(); + this._rawModuleInfos = new Map(); + // this.whenReady = Promise.resolve(true); + } + + /* + TODO check relevance + addRoot(fileOrFolder, prefix) { + this._roots.push({ + root: fileOrFolder, + prefix: prefix + }); + log.verbose(" scanning <%s> for resources (prefix='%s')", fileOrFolder, prefix); + const p = scanFileOrDir(fileOrFolder, prefix, this); + this.whenReady = this.whenReady.then( () => p ); + } + */ + + /** + * Adds a resource to the pool + * + * @param {Resource} resource + * @returns {Promise|undefined} for libraries a Promise is returned undefined otherwise + */ + addResource( resource ) { + if ( this._resourcesByName.has(resource.name) ) { + log.warn("Duplicate resource " + resource.name); + // TODO return and let the first one always win? + } + + this._resources.push(resource); + this._resourcesByName.set(resource.name, resource); + + if ( /\.library$/.test(resource.name) ) { + // read raw-module info from .library files + return resource.buffer().then( (buffer) => { + const infos = getDependencyInfos( resource.name, buffer ); + for ( const name of Object.keys(infos) ) { + this._rawModuleInfos.set(name, infos[name]); + } + }); + } + } + + /** + * Retrieves the module info + * + * @param {string} resourceName resource/module name + * @param {string} [moduleName] module name, in case it differs from the resource name (e.g. for -dbg resources) + * @returns {Promise} + */ + async getModuleInfo(resourceName, moduleName) { + let info = this._dependencyInfos.get(resourceName); + if ( info == null ) { + info = Promise.resolve().then(async () => { + const resource = await this.findResource(resourceName); + return determineDependencyInfo( resource, this._rawModuleInfos.get(moduleName || resourceName), this ); + }); + this._dependencyInfos.set(resourceName, info); + } + return info; + } + + async findResource(name) { + const resource = this._resourcesByName.get(name); + if ( resource == null ) { + // TODO: Remove throw and return null to align with ui5-fs + // This would require changes in most consuming classes + throw new Error("resource not found in pool: '" + name + "'"); + } + return resource; + } + + async findResourceWithInfo(name) { + return this.findResource(name).then( (resource) => { + return this.getModuleInfo(name).then( (info) => { + // HACK: attach info to resource + resource.info = info; + return resource; + }, (err) => resource); + }); + } + + /** + * Finds the resources based matching the given pattern + * + * @param {ResourceFilterList|RegExp} pattern + * @returns {Promise} + */ + async findResources(pattern) { + if ( pattern instanceof ResourceFilterList ) { + return this._resources.filter( (resource) => pattern.matches(resource.name) ); + } + return this._resources.filter( (resource) => pattern.test(resource.name) ); + } + + get size() { + return this._resources.length; + } + + get resources() { + return this._resources.slice(); + } + + getIgnoreMissingModules() { + return this._ignoreMissingModules; + } +} + +export default ResourcePool; + diff --git a/packages/builder/lib/lbt/utils/ASTUtils.js b/packages/builder/lib/lbt/utils/ASTUtils.js new file mode 100644 index 00000000000..76e27e27c0c --- /dev/null +++ b/packages/builder/lib/lbt/utils/ASTUtils.js @@ -0,0 +1,144 @@ + +import {Syntax} from "../utils/parseUtils.js"; + +/** + * Checks whether the given node is a string literal. + * + * If second parameter 'literal' is given, the value of the node is additionally compared with that literal. + * + * @private + * @param {ESTree} node + * @param {string} [literal] + * @returns {boolean} Whether the node is a literal and whether its value matches the given string + */ +export function isString(node, literal) { + const value = getStringValue(node); + if (value === undefined) { + return false; + } + return literal == null ? true: value === literal; +} + +export function getStringValue(node) { + if (isLiteral(node)) { + return node.value; + } else if (isTemplateLiteralWithoutExpression(node)) { + return node?.quasis?.[0]?.value?.cooked; + } else { + return undefined; + } +} + +export function isLiteral(node) { + return node && node.type === Syntax.Literal && typeof node.value === "string"; +} + +export function isTemplateLiteralWithoutExpression(node) { + return node?.type === Syntax.TemplateLiteral && + node?.expressions?.length === 0 && + node?.quasis?.length === 1; +} + +export function isBoolean(node, literal) { + if ( node == null || node.type !== Syntax.Literal || typeof node.value !== "boolean" ) { + return false; + } + return literal == null ? true : node.value === literal; +} + +export function isMethodCall(node, methodPath) { + if ( node.type !== Syntax.CallExpression ) { + return false; + } + + // BYFIELD ( BYFIELD ( BYFIELD ( a, b), c), d) + return isNamedObject(node.callee, methodPath, methodPath.length); +} + +export function isNamedObject(node, objectPath, length) { + // TODO: Support PrivateIdentifier (foo.#bar) + + // console.log("checking for named object ", node, objectPath, length); + while ( length > 1 && + node.type === Syntax.MemberExpression && + isIdentifier(node.property, objectPath[length-1]) ) { + node = node.object; + length--; + } + return length === 1 && isIdentifier(node, objectPath[0]); +} + +export function isIdentifier(node, name) { + if ( node.type === Syntax.Identifier && typeof name == "string" ) { + return name === node.name; + } else if ( node.type === Syntax.Identifier && Array.isArray(name) ) { + return name.find((name) => name === node.name || name === "*") !== undefined; + } else if ( node.type === Syntax.ObjectPattern ) { + return node.properties.filter((childnode) => isIdentifier(childnode.key, name)).length > 0; + } else if ( node.type === Syntax.ArrayPattern ) { + return node.elements.filter((childnode) => isIdentifier(childnode, name)).length > 0; + } else { + return false; + } +} + +export function getPropertyKey(property) { + if ( property.type === Syntax.SpreadElement ) { + // TODO: Support interpreting SpreadElements + return; + } else if ( property.key.type === Syntax.Identifier && property.computed !== true ) { + return property.key.name; + } else if ( property.key.type === Syntax.Literal ) { + return String(property.key.value); + } +} + +export function findOwnProperty(obj, name) { + const property = obj && obj.properties.find((property) => getPropertyKey(property) === name); + return property && property.value; +} + +export function getValue(obj, names) { + let i = 0; + while ( i < names.length ) { + if ( obj == null || obj.type !== Syntax.ObjectExpression ) { + return null; + } + obj = findOwnProperty(obj, names[i++]); + } + return obj; +} + +/** + * Converts an AST node of type 'ArrayExpression' into an array of strings, + * assuming that each item in the array literal is a string literal. + * + * Depending on the parameter skipNonStringLiterals, unexpected items + * in the array are either ignored or cause the method to fail with + * a TypeError. + * + * @param {ESTree} array + * @param {boolean } skipNonStringLiterals + * @throws {TypeError} + * @returns {string[]} + */ +export function getStringArray(array, skipNonStringLiterals) { + return array.elements.reduce( (result, item) => { + const value = getStringValue(item); + if ( value !== undefined ) { + result.push(value); + } else if ( !skipNonStringLiterals ) { + if (item.type === Syntax.TemplateLiteral) { + throw new TypeError("array element is a template literal with expressions"); + } else { + throw new TypeError("array element is not a string literal: " + item.type); + } + } + return result; + }, []); +} + +export function getLocation(args) { + // NODE-TODO include line information in future + return args[0].value; +} diff --git a/packages/builder/lib/lbt/utils/JSTokenizer.js b/packages/builder/lib/lbt/utils/JSTokenizer.js new file mode 100644 index 00000000000..7e173fcbc86 --- /dev/null +++ b/packages/builder/lib/lbt/utils/JSTokenizer.js @@ -0,0 +1,387 @@ +/* + * This is a copy of the sap/base/util/JSTokenizer.js module from OpenUI5 + * https://github.com/SAP/openui5/blob/99ddaf3e3d2b1217fba25d570235bc28cea736fd/src/sap.ui.core/src/sap/base/util/JSTokenizer.js + */ + +/* eslint-disable */ + +/*! + * ${copyright} + */ +/* + * IMPORTANT: This is a private module, its API must not be used and is subject to change. + * Code other than the OpenUI5 libraries must not introduce dependencies to this module. + */ +//sap.ui.define([], function() { + //"use strict"; + + /* + * The following code has been taken from the component JSON in JavaScript + * from Douglas Crockford which is licensed under Public Domain + * (http://www.json.org/ > JavaScript > json-2). The code contains + * local modifications. + * + * Git URL: https://github.com/douglascrockford/JSON-js/blob/42c18c621a411c3f39a81bb0a387fc50dcd738d9/json_parse.js + */ + + /** + * Tokenizer for JS values. + * + * Contains functions to consume tokens on an input string. + * + * @example + * sap.ui.require(["sap/base/util/JSTokenizer"], function(JSTokenizer){ + * JSTokenizer().parseJS("{test:'123'}"); // {test:'123'} + * }); + * + * @class sap/base/util/JSTokenizer + * @since 1.58 + * @private + * @ui5-restricted sap.ui.core + */ + var JSTokenizer = function() { + + this.at; // The index of the current character + this.ch; // The current character + this.escapee = { + '"': '"', + '\'': '\'', + '\\': '\\', + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' + }; + this.text; + }; + + + JSTokenizer.prototype.error = function(m) { + + // Call error when something is wrong. + throw { + name: 'SyntaxError', + message: m, + at: this.at, + text: this.text + }; + }; + + JSTokenizer.prototype.next = function(c) { + + // If a c parameter is provided, verify that it matches the current character. + if (c && c !== this.ch) { + this.error("Expected '" + c + "' instead of '" + this.ch + "'"); + } + + // Get the next character. When there are no more characters, + // return the empty string. + this.ch = this.text.charAt(this.at); + this.at += 1; + return this.ch; + }; + + JSTokenizer.prototype.number = function() { + + // Parse a number value. + var number, string = ''; + + if (this.ch === '-') { + string = '-'; + this.next('-'); + } + while (this.ch >= '0' && this.ch <= '9') { + string += this.ch; + this.next(); + } + if (this.ch === '.') { + string += '.'; + while (this.next() && this.ch >= '0' && this.ch <= '9') { + string += this.ch; + } + } + if (this.ch === 'e' || this.ch === 'E') { + string += this.ch; + this.next(); + if (this.ch === '-' || this.ch === '+') { + string += this.ch; + this.next(); + } + while (this.ch >= '0' && this.ch <= '9') { + string += this.ch; + this.next(); + } + } + number = +string; + if (!isFinite(number)) { + this.error("Bad number"); + } else { + return number; + } + }; + + JSTokenizer.prototype.string = function() { + + // Parse a string value. + var hex, i, string = '', quote, + uffff; + + // When parsing for string values, we must look for " and \ characters. + if (this.ch === '"' || this.ch === '\'') { + quote = this.ch; + while (this.next()) { + if (this.ch === quote) { + this.next(); + return string; + } + if (this.ch === '\\') { + this.next(); + if (this.ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(this.next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof this.escapee[this.ch] === 'string') { + string += this.escapee[this.ch]; + } else { + break; + } + } else { + string += this.ch; + } + } + } + this.error("Bad string"); + }; + + JSTokenizer.prototype.name = function() { + + // Parse a name value. + var name = '', + allowed = function(ch) { + return ch === "_" || ch === "$" || + (ch >= "0" && ch <= "9") || + (ch >= "a" && ch <= "z") || + (ch >= "A" && ch <= "Z"); + }; + + if (allowed(this.ch)) { + name += this.ch; + } else { + this.error("Bad name"); + } + + while (this.next()) { + if (this.ch === ' ') { + this.next(); + return name; + } + if (this.ch === ':') { + return name; + } + if (allowed(this.ch)) { + name += this.ch; + } else { + this.error("Bad name"); + } + } + this.error("Bad name"); + }; + + JSTokenizer.prototype.white = function() { + + // Skip whitespace. + while (this.ch && this.ch <= ' ') { + this.next(); + } + }; + + JSTokenizer.prototype.word = function() { + + // true, false, or null. + switch (this.ch) { + case 't': + this.next('t'); + this.next('r'); + this.next('u'); + this.next('e'); + return true; + case 'f': + this.next('f'); + this.next('a'); + this.next('l'); + this.next('s'); + this.next('e'); + return false; + case 'n': + this.next('n'); + this.next('u'); + this.next('l'); + this.next('l'); + return null; + } + this.error("Unexpected '" + this.ch + "'"); + }; + + //value, // Place holder for the value function. + JSTokenizer.prototype.array = function() { + + // Parse an array value. + var array = []; + + if (this.ch === '[') { + this.next('['); + this.white(); + if (this.ch === ']') { + this.next(']'); + return array; // empty array + } + while (this.ch) { + array.push(this.value()); + this.white(); + if (this.ch === ']') { + this.next(']'); + return array; + } + this.next(','); + this.white(); + } + } + this.error("Bad array"); + }; + + var object = function() { + + // Parse an object value. + var key, object = {}; + + if (this.ch === '{') { + this.next('{'); + this.white(); + if (this.ch === '}') { + this.next('}'); + return object; // empty object + } + while (this.ch) { + if (this.ch >= "0" && this.ch <= "9") { + key = this.number(); + } else if (this.ch === '"' || this.ch === '\'') { + key = this.string(); + } else { + key = this.name(); + } + this.white(); + this.next(':'); + if (Object.hasOwnProperty.call(object, key)) { + this.error('Duplicate key "' + key + '"'); + } + object[key] = this.value(); + this.white(); + if (this.ch === '}') { + this.next('}'); + return object; + } + this.next(','); + this.white(); + } + } + this.error("Bad object"); + }; + + JSTokenizer.prototype.value = function() { + + // Parse a JS value. It could be an object, an array, a string, a number, + // or a word. + this.white(); + switch (this.ch) { + case '{': + return object.call(this); + case '[': + return this.array(); + case '"': + case '\'': + return this.string(); + case '-': + return this.number(); + default: + return this.ch >= '0' && this.ch <= '9' ? this.number() : this.word(); + } + }; + + /** + * Returns the index of the current character. + * + * @private + * @returns {int} The current character's index. + */ + JSTokenizer.prototype.getIndex = function() { + return this.at - 1; + }; + + JSTokenizer.prototype.getCh = function() { + return this.ch; + }; + + JSTokenizer.prototype.init = function(sSource, iIndex) { + this.text = sSource; + this.at = iIndex || 0; + this.ch = ' '; + }; + + /** + * Advances the index in the text to iIndex. Fails if the new index + * is smaller than the previous index. + * + * @private + * @param {int} iIndex - the new index + */ + JSTokenizer.prototype.setIndex = function(iIndex) { + if (iIndex < this.at - 1) { + throw new Error("Must not set index " + iIndex + + " before previous index " + (this.at - 1)); + } + this.at = iIndex; + this.next(); + }; + + /** + * Return the parse function. It will have access to all of the above + * functions and variables. + * + * @private + * @ui5-restricted sap.ui.core + * @static + * @param {string} sSource The js source + * @param {int} iStart The start position + * @returns {object} the JavaScript object + */ + JSTokenizer.parseJS = function(sSource, iStart) { + + var oJSTokenizer = new JSTokenizer(); + var result; + oJSTokenizer.init(sSource, iStart); + result = oJSTokenizer.value(); + + if ( isNaN(iStart) ) { + oJSTokenizer.white(); + if (oJSTokenizer.getCh()) { + oJSTokenizer.error("Syntax error"); + } + return result; + } else { + return { result : result, at : oJSTokenizer.getIndex()}; + } + + }; + +// return JSTokenizer; +//}); + +export default JSTokenizer; diff --git a/packages/builder/lib/lbt/utils/ModuleName.js b/packages/builder/lib/lbt/utils/ModuleName.js new file mode 100644 index 00000000000..224c1352787 --- /dev/null +++ b/packages/builder/lib/lbt/utils/ModuleName.js @@ -0,0 +1,122 @@ + +/** + * Creates a ModuleName from a string in UI5 module name syntax. + * + * @private + * @param {string} name String that represents a UI5 module name (dot separated) + * @param {string} [suffix='.js'] Suffix to add to the resulting resource name + * @returns {string} URN representing the same resource + */ +export function fromUI5LegacyName(name, suffix) { + // UI5 only supports a few names with dots in them, anything else will be converted to slashes + if ( name.startsWith("sap.ui.thirdparty.jquery.jquery-") ) { + name = "sap/ui/thirdparty/jquery/jquery-" + name.slice("sap.ui.thirdparty.jquery.jquery-".length); + } else if ( name.startsWith("jquery.sap.") || name.startsWith("jquery-") ) { + // do nothing + } else { + name = name.replace(/\./g, "/"); + } + return name + (suffix || ".js"); +} + +export function toUI5LegacyName(path) { + if ( !path.endsWith(".js") ) { + throw new Error("can't convert a non-JS resource name " + path + " to a UI5 module name"); + } + const moduleName = path.slice(0, -3); + if ( moduleName.startsWith("sap/ui/thirdparty/jquery/jquery-") ) { + return "sap.ui.thirdparty.jquery.jquery-" + moduleName.slice("sap/ui/thirdparty/jquery/jquery-".length); + } else if ( moduleName.startsWith("jquery.sap.") || moduleName.startsWith("jquery-") ) { + return moduleName; + } else { + return moduleName.replace(/\//g, "."); + } +} + +export function fromRequireJSName(name) { + return name + ".js"; +} + +export function toRequireJSName(path) { + if ( !path.endsWith(".js") ) { + throw new Error("can't convert a non-JS resource name " + path + " to a requireJS module name"); + } + return path.slice(0, -3); // cut off '.js' +} + +const KNOWN_TYPES = /\.(properties|css|(?:(?:view\.|fragment\.)?(?:html|json|xml|js))|(?:(?:controller\.|designtime\.|support\.)?js))$/; + +export function getDebugName(name) { + const m = KNOWN_TYPES.exec(name); + if ( m && ( m[0].endsWith(".css") || m[0].endsWith(".js") ) && !name.slice(0, m.index).endsWith("-dbg") ) { + return name.slice(0, m.index) + "-dbg" + m[0]; + } + return null; +} + +export function getNonDebugName(name) { + const m = KNOWN_TYPES.exec(name); + if ( m && ( m[0].endsWith(".css") || m[0].endsWith(".js") ) && name.slice(0, m.index).endsWith("-dbg") ) { + return name.slice(0, m.index - "-dbg".length) + m[0]; + } + return null; +} + +const ANY_SPECIAL_PATH_SEGMENT = /(?:^|\/)\.+\//; +const SPECIAL_PATH_SEGMENT = /^\.+$/; + +export function resolveRelativePath(path, relativePath) { + // while has segment + // if ( segment == . ) + // ignore segment + // else if segment == .. + // remove one segment from stack, throw exception if there is none + // else + // add segment to stack + // combine segments in stack with '/' + + const match = ANY_SPECIAL_PATH_SEGMENT.exec(relativePath); + if ( match ) { + // process segments only if there is at least one special segment + let segments = []; + + const p = path.lastIndexOf("/"); + if ( match.index == 0 && p > 0 ) { + // if first segment is ./ or ../, start with parent path, otherwise start empty + segments = path.slice(0, p).split("/"); + } + + const relativePathSegments = relativePath.split("/"); + for ( let i = 0; i < relativePathSegments.length; i++ ) { + const segment = relativePathSegments[i]; + if ( SPECIAL_PATH_SEGMENT.test(segment) ) { + switch ( segment.length ) { + case 1: + // segment './' -> ignore + continue; + case 2: + // segment '../' -> navigate to parent if possible + if ( segments.length === 0 ) { + throw new Error(`Can't navigate to parent of root (${path}, ${relativePath})`); + } + segments.pop(); + break; + default: + // segment '...' or more dots: not allowed + throw new Error(`Illegal path segment '${segment}'`); + } + } else { + // normal segment: add + segments.push(segment); + } + } + // console.log("resolution of (%s,%s): %s%n", getPackagePath(), relativePath, StringUtils.join(segments, "/")); + relativePath = segments.join("/"); + } + return relativePath; +} + +export function resolveRelativeRequireJSName(path, relativeName) { + return resolveRelativePath(path, relativeName + ".js"); +} + diff --git a/packages/builder/lib/lbt/utils/escapePropertiesFile.js b/packages/builder/lib/lbt/utils/escapePropertiesFile.js new file mode 100644 index 00000000000..7652bd7fff1 --- /dev/null +++ b/packages/builder/lib/lbt/utils/escapePropertiesFile.js @@ -0,0 +1,37 @@ +import nonAsciiEscaper from "../../processors/nonAsciiEscaper.js"; + +/** + * Can be used to escape *.properties files. + * + * Input encoding is read from project configuration. + * In case the resource belongs to no project (e.g. bundler is used standalone) the default is "UTF-8". + * + * @private + * @param {Resource} resource the resource for which the content will be escaped + * @returns {Promise} resolves with the escaped string content of the given Resource + */ +export default async function(resource) { + const project = resource.getProject(); + let propertiesFileSourceEncoding = project && project.getPropertiesFileSourceEncoding(); + + if (!propertiesFileSourceEncoding) { + if (project && project.getSpecVersion().lte("1.1")) { + // default encoding to "ISO-8859-1" for old specVersions + propertiesFileSourceEncoding = "ISO-8859-1"; + } else { + // default encoding to "UTF-8" for all projects starting with specVersion 2.0 + propertiesFileSourceEncoding = "UTF-8"; + } + } + const encoding = nonAsciiEscaper.getEncodingFromAlias(propertiesFileSourceEncoding); + await nonAsciiEscaper({ + resources: [resource.resource], + options: { + encoding + } + }); + + const fileContent = await resource.buffer(); + + return fileContent.toString(); +} diff --git a/packages/builder/lib/lbt/utils/parseUtils.js b/packages/builder/lib/lbt/utils/parseUtils.js new file mode 100644 index 00000000000..fffa4a86eab --- /dev/null +++ b/packages/builder/lib/lbt/utils/parseUtils.js @@ -0,0 +1,34 @@ + +import {parse} from "espree"; + +const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty); + +/* + * NOTE: After updating the ecmaVersion: + * - Adopt JSModuleAnalyzer to handle new Syntax / VisitorKeys. + * - Adjust the JSModuleAnalyzer test "Check for consistency between VisitorKeys and EnrichedVisitorKeys" + * (See comments in test for details) +*/ +export const ecmaVersion = 2023; + +export function parseJS(code, userOptions = {}) { + // allowed options and their defaults + const options = { + comment: false, + ecmaVersion, + range: false, + sourceType: "script", + }; + + // validate and assign options + for (const [name, value] of Object.entries(userOptions)) { + if (!hasOwn(options, name)) { + throw new TypeError(`Allowed parser options are ${Object.keys(options)}, but not '${name}'`); + } + options[name] = value; + } + + return parse(code, options); +} + +export {Syntax, VisitorKeys} from "espree"; diff --git a/packages/builder/lib/lbt/utils/stringUtils.js b/packages/builder/lib/lbt/utils/stringUtils.js new file mode 100644 index 00000000000..92900683aa8 --- /dev/null +++ b/packages/builder/lib/lbt/utils/stringUtils.js @@ -0,0 +1,17 @@ +const strReplacements = { + "\r": "\\r", + "\t": "\\t", + "\n": "\\n", + "'": "\\'", + "\\": "\\\\" +}; + +export function makeStringLiteral(str) { + return "'" + String(str).replace(/['\r\n\t\\]/g, function(char) { + return strReplacements[char]; + }) + "'"; +} + +export function removeHashbang(str) { + return str.replace(/^#!(.*)/, ""); +} diff --git a/packages/builder/lib/processors/bootstrapHtmlTransformer.js b/packages/builder/lib/processors/bootstrapHtmlTransformer.js new file mode 100644 index 00000000000..cd3b5f714d1 --- /dev/null +++ b/packages/builder/lib/processors/bootstrapHtmlTransformer.js @@ -0,0 +1,41 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:bootstrapHtmlTransformer"); +import * as cheerio from "cheerio"; + +/** + * @module @ui5/builder/processors/bootstrapHtmlTransformer + */ + +/** + * Transforms the UI5 bootstrap of a HTML resource files. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed + * @param {object} parameters.options Options + * @param {string} parameters.options.src Bootstrap "src" that should be used + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with the cloned resources + */ +export default function({resources, options: {src}}) { + async function processResource(resource) { + const content = await resource.getString(); + const $ = cheerio.load(content); + const bootstrapScript = $("script#sap-ui-bootstrap"); + if (bootstrapScript.length === 1) { + bootstrapScript.attr("src", src); + resource.setString($.html()); + } else if (bootstrapScript.length > 1) { + log.warn("Skipping bootstrap transformation. " + + "Found multiple bootstrap script tags with id=sap-ui-bootstrap."); + } else { + log.warn("Skipping bootstrap transformation. " + + "Could not find bootstrap script tag with id=sap-ui-bootstrap."); + } + return resource; + } + + return Promise.all(resources.map(processResource)); +} diff --git a/packages/builder/lib/processors/bundlers/flexChangesBundler.js b/packages/builder/lib/processors/bundlers/flexChangesBundler.js new file mode 100644 index 00000000000..04827f4a7d6 --- /dev/null +++ b/packages/builder/lib/processors/bundlers/flexChangesBundler.js @@ -0,0 +1,143 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:bundlers:flexChangesBundler"); +import {createResource} from "@ui5/fs/resourceFactory"; + +/** + * @public + * @module @ui5/builder/processors/bundlers/flexChangesBundler + */ + +/** + * Bundles all supplied changes. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed + * @param {object} parameters.options Options + * @param {string} parameters.options.pathPrefix Prefix for bundle path + * @param {string} parameters.options.hasFlexBundleVersion true if minUI5Version >= 1.73 than + * create flexibility-bundle.json + * @param {object} [parameters.existingFlexBundle={}] Object with existing flexibility-bundle.json + * to merge with new changes + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with flex changes bundle resources + */ +export default function({resources, options: {pathPrefix, hasFlexBundleVersion}, existingFlexBundle = {}}) { + let bundleName = "changes-bundle.json"; + + function sortByTimeStamp(a, b) { + return a.creation > b.creation ? 1 : -1; + } + + /** + * bundle changes resource to json string + * + * @param {Array} changesContent Array of resources files + * @returns {string} Json sting of changes and control variants + */ + function sortAndStringifyInFlexFormat(changesContent) { + changesContent = changesContent.sort(sortByTimeStamp); + const changes = []; + const variantDependentControlChanges = []; + const compVariants = []; + const variants = []; + const variantChanges = []; + const variantManagementChanges = []; + + changesContent.forEach(function(content) { + if (content.layer === "VENDOR") { + content.support.user = "SAP"; + } + switch (content.fileType) { + case "change": + if (content.appDescriptorChange && (content.appDescriptorChange === "true" || + content.appDescriptorChange == true)) { + break; + } + if (content.variantReference && content.variantReference !== "") { + variantDependentControlChanges.push(content); + } else { + changes.push(content); + } + break; + case "variant": + compVariants.push(content); + break; + case "ctrl_variant": + variants.push(content); + break; + case "ctrl_variant_change": + variantChanges.push(content); + break; + case "ctrl_variant_management_change": + variantManagementChanges.push(content); + break; + } + }); + + if (!hasFlexBundleVersion && (compVariants.length != 0 || variants.length != 0 || variantChanges.length != 0 || + variantDependentControlChanges.length != 0 || variantManagementChanges.length != 0)) { + throw new Error( + "There are some control variant changes in the changes folder. This only works with a " + + "minUI5Version 1.73.0. Please update the minUI5Version in the manifest.json to 1.73.0 or higher"); + } + // create changes-bundle.json + if (!hasFlexBundleVersion) { + return JSON.stringify(changes); + } else { + bundleName = "flexibility-bundle.json"; + let newChangeFormat = { + changes, + compVariants, + variants, + variantChanges, + variantDependentControlChanges, + variantManagementChanges + }; + if (Object.keys(existingFlexBundle).length > 0) { + newChangeFormat = mergeFlexChangeBundles(newChangeFormat); + } + return JSON.stringify(newChangeFormat); + } + } + + /** + * Merge new and existing bundles + * + * @param {object} newFlexBundle Object with new content of flexibility-bundle.json + * @returns {object} Object with merged content of new and existing flexibility-bundle.json + */ + function mergeFlexChangeBundles(newFlexBundle) { + const result = {}; + + Object.keys(newFlexBundle).forEach((key) => { + if (existingFlexBundle[key] && Array.isArray(existingFlexBundle[key])) { + result[key] = existingFlexBundle[key].concat(newFlexBundle[key]); + } else { + result[key] = newFlexBundle[key]; + } + }); + + return result; + } + + return Promise.all(resources.map((resource) => { + return resource.getBuffer().then((buffer) => { + return JSON.parse(buffer.toString()); + }); + })).then((changesContent) => { + const nNumberOfChanges = changesContent.length; + log.info("Changes collected. Number of changes: " + nNumberOfChanges); + const result = []; + if (nNumberOfChanges > 0) { + changesContent = sortAndStringifyInFlexFormat(changesContent); + result.push(createResource({ + path: `${pathPrefix}/changes/${bundleName}`, + string: changesContent + })); + } + return result; + }); +} diff --git a/packages/builder/lib/processors/bundlers/moduleBundler.js b/packages/builder/lib/processors/bundlers/moduleBundler.js new file mode 100644 index 00000000000..4ce22607ed6 --- /dev/null +++ b/packages/builder/lib/processors/bundlers/moduleBundler.js @@ -0,0 +1,202 @@ +import BundleBuilder from "../../lbt/bundle/Builder.js"; +import LocatorResourcePool from "../../lbt/resources/LocatorResourcePool.js"; +import EvoResource from "@ui5/fs/Resource"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:bundlers:moduleBundler"); + +/** + * @public + * @module @ui5/builder/processors/bundlers/moduleBundler + */ + +/** + * A ModuleBundleDefinitionSection specifies the embedding mode (either 'provided', 'raw', 'preload', 'require' + * or 'bundleInfo') and lists the resources that should be in- or excluded from the section. + *

+ * Module bundle section modes
+ *

    + *
  • + * provided: A section of mode 'provided' defines a set of modules that should not be included in + * the bundle file itself, but which should be assumed to be already loaded (or 'provided') by the environment into + * which the bundle module is loaded. + *
  • + *
  • + * raw: A 'raw' section determines the set of modules that should be embedded, sorts them according + * to their dependencies and writes them out 1:1 without any transformation or wrapping (raw). Only JavaScript + * sources can be embedded in a raw section. + *
  • + *
  • + * preload: A 'preload' section packages resources that should be stored in the preload cache in the + * client. They can embed any textual resource type (JavaScript, XML, JSON and .properties files) that the + * bundling supports. UI5 modules are wrapped into a 'sap.ui.predefine' call. Other JavaScript modules will be + * embedded into a 'jQuery.sap.registerPreload' call, or in a "sap.ui.require.preload" call when + * the ui5loader is available. + *
  • + *
  • + * require: A `require` section is transformed into a `sap.ui.require` call with all the dependencies + * resolved. This module comes with an `async` flag. When set to false, the modules + * are loaded using `sap.ui.requireSync` instead of `sap.ui.require`. + * **Note:** The `sap.ui.requireSync` API is not available in UI5 version 2.x. + *
  • + *
  • + * bundleInfo: A 'bundleInfo' section describes the content of another named bundle. This information + * is transformed into a ui5loader-"bundlesUI5" configuration. + * At runtime, if a module is known to be contained in a bundle, the loader will require that bundle before + * the module itself. + * This requires the ui5loader to be available at build time and UI5 version 1.74.0 or higher at runtime. + *
  • + *
+ *

+ * + * @public + * @typedef {object} ModuleBundleDefinitionSection + * @property {string} mode The embedding mode. Either 'provided', 'raw', 'preload', 'require' or 'bundleInfo' + * @property {string[]} filters List of modules declared as glob patterns (resource name patterns) that should be + * in- or excluded. + * A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk, + * denote an arbitrary number of characters or folder names. + * Excludes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later + * exclusion overrides an earlier inclusion, and vice versa. + * @example List of modules as glob patterns that should be in- or excluded + * // Includes everything from "some/path/to/module/", + * // but excludes the subfolder "some/path/to/module/to/be/excluded/" + * const section = { + * "filters": [ + * "some/path/to/module/", + * "!some/path/to/module/to/be/excluded/" + * ] + * }; + * + * @property {boolean} [resolve=false] Whether (transitive) dependencies of modules that match the given filters + * should be resolved and added to the module set + * @property {boolean} [resolveConditional=false] Whether conditional dependencies of modules should be resolved + * and added to the module set for this section + * @property {boolean} [renderer=false] Whether renderers for controls should be added to the module set + * @property {boolean} [declareRawModules=false] Whether raw modules should be declared after jQuery.sap.global + * became available. With the usage of the ui5loader, this flag should be set to 'false' + * @property {boolean} [sort=true] Whether the modules should be sorted by their dependencies + * @property {boolean} [async=true] Whether the `require` section of the module should be loaded asynchronously. + * When set to true, the modules are loaded using a single `sap.ui.require` call instead of multiple + * `sap.ui.requireSync` calls. + * The latter API is not available in UI5 version 2.x. + * **Note:** This property is available only for `mode=require`. + */ + +/* eslint-disable max-len */ +/** + * Module bundle definition + * + * @public + * @typedef {object} ModuleBundleDefinition + * @property {string} name The module bundle name + * @property {string[]} [defaultFileTypes=[".js", ".control.xml", ".fragment.html", ".fragment.json", ".fragment.xml", ".view.html", ".view.json", ".view.xml"]] + * List of default file types to be included in the bundle + * @property {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinitionSection[]} sections List of module bundle definition sections. + */ +/* eslint-enable max-len */ + +/** + * Module bundle options + * + * @public + * @typedef {object} ModuleBundleOptions + * @property {boolean} [optimize=true] Whether the module bundle gets minified + * @property {boolean} [sourceMap=true] Whether to generate a source map file for the bundle + * @property {boolean} [decorateBootstrapModule=false] If set to 'false', bootable bundles won't be decorated + * with an optimization marker + * @property {boolean} [addTryCatchRestartWrapper=false] Whether to wrap bootable bundles with + * a try/catch to filter out "Restart" errors + * @property {number} [numberOfParts=1] The number of parts the module bundle should be splitted + * @property {boolean} [ignoreMissingModules=false] When searching for modules which are optional for further + * processing, do not throw in case they are missing + */ + +/** + * Result set + * + * @public + * @typedef {object} ModuleBundlerResult + * @property {@ui5/fs/Resource} bundle Bundle resource + * @property {@ui5/fs/Resource} sourceMap Source Map + */ + +/* eslint-disable max-len */ +/** + * Legacy module bundler. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources Resources + * @param {object} parameters.options Options + * @param {object} [parameters.options.moduleNameMapping] + Optional mapping of resource paths to module name in order to overwrite the default determination + * @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinition} parameters.options.bundleDefinition Module + bundle definition + * @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleOptions} [parameters.options.bundleOptions] Module + bundle options + * @param {string} [parameters.options.targetUi5CoreVersion] Optional semver compliant sap.ui.core project version, e.g '2.0.0'. + This allows the bundler to make assumptions on available runtime APIs. + Omit if the ultimate UI5 version at runtime is unknown or can't be determined. + * @param {boolean} [parameters.options.allowStringBundling=false] Optional flag to allow bundling of modules as a string. + * @returns {Promise} + * Promise resolving with module bundle resources + */ +/* eslint-enable max-len */ +export default function({resources, options: { + bundleDefinition, bundleOptions, moduleNameMapping, targetUi5CoreVersion, allowStringBundling = false +}}) { + // Apply defaults without modifying the passed object + bundleOptions = Object.assign({}, { + optimize: true, + sourceMap: true, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false, + numberOfParts: 1, + ignoreMissingModules: false + }, bundleOptions); + + // bundleDefinition's defaults get applied in the corresponding standard tasks + + const pool = new LocatorResourcePool({ + ignoreMissingModules: bundleOptions.ignoreMissingModules + }); + const builder = new BundleBuilder(pool, targetUi5CoreVersion, allowStringBundling); + + if (log.isLevelEnabled("verbose")) { + log.verbose(`Generating bundle:`); + log.verbose(`bundleDefinition: ${JSON.stringify(bundleDefinition, null, 2)}`); + log.verbose(`bundleOptions: ${JSON.stringify(bundleOptions, null, 2)}`); + } + return pool.prepare( resources, moduleNameMapping ). + then( () => builder.createBundle(bundleDefinition, bundleOptions) ). + then( (results) => { + let bundles; + if (results instanceof Array) { + bundles = results; + } else { + bundles = [results]; + } + + return Promise.all(bundles.map((bundleObj) => { + if ( bundleObj ) { + const {name, content, sourceMap} = bundleObj; + // console.log("creating bundle as '%s'", "/resources/" + name); + const res = {}; + res.bundle = new EvoResource({ + path: "/resources/" + name, + string: content + }); + if (sourceMap) { + res.sourceMap = new EvoResource({ + path: "/resources/" + name + ".map", + string: sourceMap + }); + } + return res; + } + })); + }); +} diff --git a/packages/builder/lib/processors/jsdoc/apiIndexGenerator.js b/packages/builder/lib/processors/jsdoc/apiIndexGenerator.js new file mode 100644 index 00000000000..beda499910b --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/apiIndexGenerator.js @@ -0,0 +1,58 @@ +import {createResource} from "@ui5/fs/resourceFactory"; +import createIndex from "./lib/createIndexFiles.js"; + +/** + * @public + * @module @ui5/builder/processors/jsdoc/apiIndexGenerator + */ + +/** + * Compiles API index resources from all api.json resources available in the given test resources directory + * as created by the [sdkTransformer]{@link @ui5/builder/processors/sdkTransformer} processor. + * The resulting index resources (e.g. api-index.json, api-index-deprecated.json, + * api-index-experimental.json and api-index-since.json) are mainly to be used in the SDK. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {string} parameters.versionInfoPath Path to sap-ui-version.json resource + * @param {string} parameters.testResourcesRootPath Path to /test-resources root directory in the + * given fs + * @param {string} parameters.targetApiIndexPath Path to create the generated API index JSON resource for + * @param {string} parameters.targetApiIndexDeprecatedPath Path to create the generated API index "deprecated" JSON + * resource for + * @param {string} parameters.targetApiIndexExperimentalPath Path to create the generated API index "experimental" JSON + * resource for + * @param {string} parameters.targetApiIndexSincePath Path to create the generated API index "since" JSON resource for + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or + * custom [fs interface]{@link module:@ui5/fs/fsInterface} to use + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with created resources api-index.json, + * api-index-deprecated.json, api-index-experimental.json and + * api-index-since.json (names depend on the supplied paths) + */ +const apiIndexGenerator = async function({ + versionInfoPath, testResourcesRootPath, targetApiIndexPath, targetApiIndexDeprecatedPath, + targetApiIndexExperimentalPath, targetApiIndexSincePath, fs +} = {}) { + if (!versionInfoPath || !testResourcesRootPath || !targetApiIndexPath || !targetApiIndexDeprecatedPath || + !targetApiIndexExperimentalPath || !targetApiIndexSincePath || !fs) { + throw new Error("[apiIndexGenerator]: One or more mandatory parameters not provided"); + } + + const resourceMap = await createIndex(versionInfoPath, testResourcesRootPath, targetApiIndexPath, + targetApiIndexDeprecatedPath, targetApiIndexExperimentalPath, targetApiIndexSincePath, { + fs, + returnOutputFiles: true + }); + + return Object.keys(resourceMap).map((resPath) => { + return createResource({ + path: resPath, + string: resourceMap[resPath] + }); + }); +}; + +export default apiIndexGenerator; diff --git a/packages/builder/lib/processors/jsdoc/jsdocGenerator.js b/packages/builder/lib/processors/jsdoc/jsdocGenerator.js new file mode 100644 index 00000000000..b3664f3c922 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/jsdocGenerator.js @@ -0,0 +1,180 @@ +import {spawn} from "node:child_process"; +import fs from "graceful-fs"; +import path from "node:path"; +import {promisify} from "node:util"; +const writeFile = promisify(fs.writeFile); +import {createAdapter} from "@ui5/fs/resourceFactory"; +import {fileURLToPath} from "node:url"; + +/** + * @public + * @module @ui5/builder/processors/jsdoc/jsdocGenerator + */ + +/** + * JSDoc generator + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {string} parameters.sourcePath Path of the source files to be processed + * @param {string} parameters.targetPath Path to write any output files + * @param {string} parameters.tmpPath Path to write temporary and debug files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.namespace Namespace to build (e.g. some/project/name) + * @param {string} parameters.options.version Project version + * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with newly created resources + */ +export default async function jsdocGenerator( + {sourcePath, targetPath, tmpPath, options: {projectName, namespace, version, variants}} = {} +) { + if (!sourcePath || !targetPath || !tmpPath || !projectName || !namespace || !version) { + throw new Error("[jsdocGenerator]: One or more mandatory parameters not provided"); + } + + if (!variants || variants.length === 0) { + variants = ["apijson"]; + } + + const config = await jsdocGenerator._generateJsdocConfig({ + targetPath, + tmpPath, + namespace, + projectName, + version, + variants + }); + + const configPath = await jsdocGenerator._writeJsdocConfig(tmpPath, config); + + await jsdocGenerator._buildJsdoc({ + sourcePath, + configPath + }); + + const fsTarget = createAdapter({ + fsBasePath: targetPath, + virBasePath: "/" + }); + + // create resources from the output files + return Promise.all([ + fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) + // fsTarget.byPath(`/libraries/${options.projectName}.js`) + ]).then((res) => res.filter(($)=>$)); +} + + +/** + * Generate jsdoc-config.json content + * + * @private + * @param {object} parameters Parameters + * @param {string} parameters.targetPath Path to write any output files + * @param {string} parameters.tmpPath Path to write temporary and debug files + * @param {string} parameters.projectName Project name + * @param {string} parameters.version Project version + * @param {string} parameters.namespace Namespace to use (e.g. some/project/name) + * @param {Array} parameters.variants JSDoc variants to be built + * @returns {string} jsdoc-config.json content string + */ +async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) { + // Backlash needs to be escaped as double-backslash + // This is not only relevant for win32 paths but also for + // Unix directory names that contain a backslash in their name + const backslashRegex = /\\/g; + + // Resolve path to this script to get the path to the JSDoc extensions folder + const jsdocPath = path.normalize(import.meta.dirname); + const pluginPath = path.join(jsdocPath, "lib", "ui5", "plugin.js").replace(backslashRegex, "\\\\"); + const templatePath = path.join(jsdocPath, "lib", "ui5", "template").replace(backslashRegex, "\\\\"); + const destinationPath = path.normalize(tmpPath).replace(backslashRegex, "\\\\"); + const jsapiFilePath = path.join(targetPath, "libraries", projectName + ".js").replace(backslashRegex, "\\\\"); + const apiJsonFolderPath = path.join(tmpPath, "dependency-apis").replace(backslashRegex, "\\\\"); + const apiJsonFilePath = + path.join(targetPath, "test-resources", path.normalize(namespace), "designtime", "api.json") + .replace(backslashRegex, "\\\\"); + + // Note: While the projectName could also be used here, it is not ensured that it fits to + // the library namespace. + // As the "uilib" information is used to check for certain constraints it must be aligned with + // the technical namespace that is used in the folder-structure, library.js and for the + // sap.ui.base.Object based classes. + const uilib = namespace.replace(/\//g, "."); + + const config = `{ + "plugins": ["${pluginPath}"], + "opts": { + "recurse": true, + "lenient": true, + "template": "${templatePath}", + "ui5": { + "saveSymbols": true + }, + "destination": "${destinationPath}" + }, + "templates": { + "ui5": { + "variants": ${JSON.stringify(variants)}, + "version": "${version}", + "uilib": "${uilib}", + "jsapiFile": "${jsapiFilePath}", + "apiJsonFolder": "${apiJsonFolderPath}", + "apiJsonFile": "${apiJsonFilePath}" + } + } + }`; + return config; +} + +/** + * Write jsdoc-config.json to file system + * + * @private + * @param {string} targetDirPath Directory Path to write the jsdoc-config.json file to + * @param {string} config jsdoc-config.json content + * @returns {string} Full path to the written jsdoc-config.json file + */ +async function writeJsdocConfig(targetDirPath, config) { + const configPath = path.join(targetDirPath, "jsdoc-config.json"); + await writeFile(configPath, config); + return configPath; +} + + +/** + * Execute JSDoc build by spawning JSDoc as an external process + * + * @private + * @param {object} parameters Parameters + * @param {string} parameters.sourcePath Project resources (input for JSDoc generation) + * @param {string} parameters.configPath Full path to jsdoc-config.json file + * @returns {Promise} + */ +async function buildJsdoc({sourcePath, configPath}) { + const args = [ + fileURLToPath(import.meta.resolve("jsdoc/jsdoc.js")), + "-c", + configPath, + "--verbose", + sourcePath + ]; + const exitCode = await new Promise((resolve /* , reject */) => { + const child = spawn("node", args, { + stdio: ["ignore", "ignore", "inherit"] + }); + child.on("close", resolve); + }); + + if (exitCode !== 0) { + throw new Error(`JSDoc reported an error, check the log for issues (exit code: ${exitCode})`); + } +} + +jsdocGenerator._generateJsdocConfig = generateJsdocConfig; +jsdocGenerator._writeJsdocConfig = writeJsdocConfig; +jsdocGenerator._buildJsdoc = buildJsdoc; diff --git a/packages/builder/lib/processors/jsdoc/lib/createIndexFiles.js b/packages/builder/lib/processors/jsdoc/lib/createIndexFiles.js new file mode 100644 index 00000000000..dbc3aec78b2 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/createIndexFiles.js @@ -0,0 +1,541 @@ +/* + * Node script to create cross-library API index files for use in the UI5 SDKs. + * + * (c) Copyright 2025 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +"use strict"; +const path = require("path"); +const log = (function() { + try { + return require("@ui5/logger").getLogger("builder:processors:jsdoc:create-api-index"); + } catch (error) { + /* eslint-disable no-console */ + return { + info: function info(...msg) { + console.log("[INFO]", ...msg); + }, + error: function error(...msg) { + console.error(...msg); + } + }; + /* eslint-enable no-console */ + } +}()); + +function createIndexFiles(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince, options) { + const fs = options && options.fs || require("fs"); + const returnOutputFiles = options && !!options.returnOutputFiles; + + log.info("creating API index files"); + log.info(" sap-ui-version.json: " + versionInfoFile); + log.info(" unpacked test-resources: " + unpackedTestresourcesRoot); + log.info(" target file: " + targetFile); + log.info(" target file deprecated: " + targetFileDeprecated); + log.info(" target file experimental: " + targetFileExperimental); + log.info(" target file since: " + targetFileSince); + if (options && options.fs) { + log.info("Using custom fs"); + } + if (returnOutputFiles) { + log.info("Returning output files instead of writing to fs."); + } + log.info(""); + + // Deprecated, Experimental and Since collections + const oListCollection = { + deprecated: { + noVersion: { + apis: [] + } + }, + experimental: { + noVersion: { + apis: [] + } + }, + since: {} + }; + + function readJSONFile(file) { + return new Promise(function (resolve, reject) { + fs.readFile(file, 'utf8', function (err, data) { + if (err) { + reject(err); + } else if (data.trim() === "") { + // Handle empty files scenario + resolve({}); + } else { + resolve(JSON.parse(String(data))); + } + }); + }); + } + + function mkdirSync(dir) { + if (dir && !fs.existsSync(dir)) { + mkdirSync( path.dirname(dir) ); + fs.mkdirSync(dir); + } + } + + function writeJSON(file, content) { + return new Promise(function(resolve,reject) { + // Create dir if it does not exist + mkdirSync( path.dirname(file) ); + fs.writeFile(file, JSON.stringify(content), "utf-8", function(err) { + if ( err ) { + reject(err); + return; + } + resolve(true); + }); + }); + } + + /* + * Extracts main symbol information from a library api.json. + * Also collects deprecated, experimental and since api's. + * Returns a promise that resolves with an array of symbols. + */ + function createSymbolSummaryForLib(lib) { + const file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); + + return readJSONFile(file).then(function (apijson) { + if (!apijson.hasOwnProperty("symbols") || !Array.isArray(apijson.symbols)) { + // Ignore libraries with invalid api.json content like empty object or non-array "symbols" property. + return []; + } + return apijson.symbols.map((symbol) => { + const oReturn = { + name: symbol.name, + kind: symbol.kind, + visibility: symbol.visibility, + "extends": symbol.extends, + "implements": symbol.implements, + lib: lib + }; + // We add deprecated member only when the control is deprecated to keep file size at check + if (symbol.deprecated) { + oReturn.deprecated = true; + } + + if (symbol.experimental && !symbol.deprecated) { + oReturn.experimental = true; + } else { + oReturn.experimental = false; + } + + collectLists(symbol); + return oReturn; + }); + }); + } + + /* + * Collects Deprecated, Experimental and Since data from passed symbol + * including symbol itself, methods and events. + */ + function collectLists(oSymbol) { + + function addData(oDataType, oEntityObject, sObjectType, sSymbolName) { + const sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since; + const sText = oDataType !== "since" ? oEntityObject[oDataType].text : oEntityObject.description; + const oData = { + control: sSymbolName, + text: sText || undefined, + type: sObjectType, + "static": !!oEntityObject.static, + visibility: oEntityObject.visibility + }; + + // For class we skip entityName + if (sObjectType !== "class") { + oData.entityName = oEntityObject.name; + } + + if (sSince && sSince !== "undefined" /* Sometimes sSince comes as string "undefined" */) { + // take only major and minor versions + const sVersion = sSince.split(".").slice(0, 2).join("."); + + oData.since = sSince; + + if (!oListCollection[oDataType][sVersion]) { + oListCollection[oDataType][sVersion] = { + name: sVersion, + apis: [] + }; + } + + oListCollection[oDataType][sVersion].apis.push(oData); + } else if (oDataType !== "since" /* noVersion does not make sense for since and will fail */) { + oListCollection[oDataType].noVersion.apis.push(oData); + } + } + + // Classes + if (oSymbol.deprecated) { + addData("deprecated", oSymbol, "class", oSymbol.name); + } + + if (oSymbol.experimental && !oSymbol.deprecated) { + addData("experimental", oSymbol, "class", oSymbol.name); + } + + if (oSymbol.since && oSymbol.since !== "undefined" /* Sometimes sSince comes as string "undefined" */) { + addData("since", oSymbol, "class", oSymbol.name); + } + + // Methods + oSymbol.methods?.forEach((oMethod) => { + if (oMethod.deprecated) { + addData("deprecated", oMethod, "methods", oSymbol.name); + } + + if (oMethod.experimental && !oSymbol.deprecated) { + addData("experimental", oMethod, "methods", oSymbol.name); + } + + if (oMethod.since) { + addData("since", oMethod, "methods", oSymbol.name); + } + }); + + // Events + oSymbol.events?.forEach((oEvent) => { + if (oEvent.deprecated) { + addData("deprecated", oEvent, "events", oSymbol.name); + } + + if (oEvent.experimental && !oSymbol.deprecated) { + addData("experimental", oEvent, "events", oSymbol.name); + } + + if (oEvent.since) { + addData("since", oEvent, "events", oSymbol.name); + } + }); + + } + + function deepMerge(arrayOfArrays) { + return arrayOfArrays.reduce((array, items) => { + array.push.apply(array, items); + return array; + }, []); + } + + function expandHierarchyInfo(symbols) { + const byName = new Map(); + symbols.forEach((symbol) => { + byName.set(symbol.name, symbol); + }); + symbols.forEach((symbol) => { + const parent = symbol.extends && byName.get(symbol.extends); + if (parent) { + parent.extendedBy = parent.extendedBy || []; + parent.extendedBy.push({ + name: symbol.name, + visibility: symbol.visibility + }); + } + if (symbol.implements) { + symbol.implements.forEach((intfName) => { + const intf = byName.get(intfName); + if (intf) { + intf.implementedBy = intf.implementedBy || []; + intf.implementedBy.push({ + name: symbol.name, + visibility: symbol.visibility + }); + } + }); + } + }); + return symbols; + } + + function convertListToTree(symbols) { + const aTree = []; + + // Filter out excluded libraries + symbols = symbols.filter(({lib}) => ["sap.ui.documentation"].indexOf(lib) === -1); + + // Create treeName and displayName + symbols.forEach((oSymbol) => { + oSymbol.treeName = oSymbol.name.replace(/^module:/, "").replace(/\//g, "."); + oSymbol.displayName = oSymbol.treeName.split(".").pop(); + }); + + // Create missing - virtual namespaces + symbols.forEach((oSymbol) => { + oSymbol.treeName.split(".").forEach((sPart, i, a) => { + const sName = a.slice(0, (i + 1)).join("."); + + if (!symbols.find((o) => o.treeName === sName)) { + symbols.push({ + name: sName, + treeName: sName, + displayName: sPart, + lib: oSymbol.lib, + kind: "namespace", + visibility: "public" // Virtual namespace are always public + }); + } + }); + }); + + // Discover parents + symbols.forEach((oSymbol) => { + const aParent = oSymbol.treeName.split("."); + + // Extract parent name + aParent.pop(); + const sParent = aParent.join("."); + + // Mark parent + if (symbols.find(({treeName}) => treeName === sParent)) { + oSymbol.parent = sParent; + } + }); + + // Sort the list before building the tree + symbols.sort((a, b) => { + const sA = a.treeName.toUpperCase(), + sB = b.treeName.toUpperCase(); + + if (sA < sB) {return -1;} + if (sA > sB) {return 1;} + return 0; + }); + + // Build tree + symbols.forEach((oSymbol) => { + if (oSymbol.parent) { + const oParent = symbols.find(({treeName}) => treeName === oSymbol.parent); + + if (!oParent.nodes) {oParent.nodes = [];} + oParent.nodes.push(oSymbol); + } else { + aTree.push(oSymbol); + } + }); + + // Custom sort first level tree items - "sap" namespace should be on top + aTree.sort((a, b) => { + const sA = a.displayName.toUpperCase(), + sB = b.displayName.toUpperCase(); + + if (sA === "SAP") {return -1;} + if (sB === "SAP") {return 1;} + if (sA < sB) {return -1;} + if (sA > sB) {return 1;} + + return 0; + }); + + // walk the tree *from bottom to top* + // in order to detect all parent nodes + // that should be marked as content-deprecated + // because their children are explicitly deprecated + toChildrenFirstArray(aTree).forEach(markDeprecatedNodes); + + // Clean tree - keep file size down + function cleanTree (oSymbol) { + delete oSymbol.treeName; + delete oSymbol.parent; + if (oSymbol.nodes) { + oSymbol.nodes.forEach((o) => cleanTree(o)); + } + } + aTree.forEach((o) => cleanTree(o)); + + return aTree; + } + + /** + * Creates an array of all tree nodes, + * where the child nodes precede the parent nodes + * @param aTree + * @returns {Array} + */ + function toChildrenFirstArray(aTree) { + var aChildrenFirst = []; + function addToLeafsFirst(node) { + if (node.nodes) { + node.nodes.forEach(function(child) { + addToLeafsFirst(child); + }); + } + aChildrenFirst.push(node); + } + aTree.forEach(function(parent) { + addToLeafsFirst(parent); + }); + return aChildrenFirst; + } + + /** + * Sets the bAllContentDeprecated flag of each symbol + * + * The bAllContentDeprecated flag differs from the already existing deprecated flag + * in the following respect: + * + * 1) if a node is deprecated => all its children should be marked as bAllContentDeprecated + * (even if not explicitly deprecated in their JSDoc) + * 2) if all children of the node are deprecated => that node should also be marked as bAllContentDeprecated + * (even if not explicitly deprecated in its JSDoc) + * 3) if a node is explicitly deprecated in its JSDoc => it should also be marked as bAllContentDeprecated + * (for consistency) + * + * @param oSymbol + */ + function markDeprecatedNodes(oSymbol) { + // 1. If the symbol is deprecated all content in it should be also deprecated + if (oSymbol.deprecated) { + // 2. If all content in the symbol is deprecated, flag should explicitly be passed to its child nodes. + propagateFlags(oSymbol, { bAllContentDeprecated: true }); + } else { + // 3. If all children are deprecated, then the parent is marked as content-deprecated + oSymbol.bAllContentDeprecated = !!oSymbol.nodes && oSymbol.nodes.every((node) => node.bAllContentDeprecated); + } + } + + /** + * Merges the set of flags from oFlags into the given oSymbol + * @param oSymbol + * @param oFlags + */ + function propagateFlags(oSymbol, oFlags) { + Object.assign(oSymbol, oFlags); + if (oSymbol.nodes) { + oSymbol.nodes.forEach((node) => { + propagateFlags(node, oFlags); + }); + } + } + + function createOverallIndex() { + let version = "0.0.0"; + const filesToReturn = {}; + + var p = readJSONFile(versionInfoFile) + .then((versionInfo) => { + version = versionInfo.version; + return Promise.all( + versionInfo.libraries.map( + (lib) => createSymbolSummaryForLib(lib.name).catch((err) => { + // ignore 'file not found' errors as some libs don't have an api.json (themes, server libs) + if (err.code === 'ENOENT') { + return []; + } + throw err; + }) + ) + ); + }) + .then(deepMerge) + .then(expandHierarchyInfo) + .then(convertListToTree) + .then((symbols) => { + const result = { + "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api-index.json/1.0", + version: version, + library: "*", + symbols: symbols + }; + if (returnOutputFiles) { + filesToReturn[targetFile] = JSON.stringify(result); + } else { + return writeJSON(targetFile, result); + } + }) + .then(() => { + /* Lists - modify and cleanup */ + const sortList = function (oList) { + /* Sorting since records */ + const aKeys = Object.keys(oList), + oSorted = {}; + + aKeys.sort((a, b) => { + const aA = a.split("."), + aB = b.split("."); + + if (a === "noVersion") { + return 1; /* No version always at end of list */ + } + + if (b === "noVersion") { + return -1; /* No version always at end of list */ + } + + // Normalize old versions 1.4 to 1.04 for proper sorting + a = [aA[0], ('0' + aA[1]).slice(-2)].join(""); + b = [aB[0], ('0' + aB[1]).slice(-2)].join(""); + + // Sort descending + return parseInt(b) - parseInt(a); + }); + + aKeys.forEach((sKey) => { + oSorted[sKey] = oList[sKey]; + oSorted[sKey].apis.sort((a,b) => { + const keyA = `${a.control}|${a.entityName || ""}`.toLowerCase(); + const keyB = `${b.control}|${b.entityName || ""}`.toLowerCase(); + if ( keyA === keyB ) { + if ( a.static === b.static ) { + return 0; + } + return a.static ? -1 : 1; + } + return keyA < keyB ? -1 : 1; + }); // sort entries within the same version alphabetically (case insensitive) + }); + + return oSorted; + }; + + /* Since */ + oListCollection.since = sortList(oListCollection.since); + + /* Deprecated */ + oListCollection.deprecated = sortList(oListCollection.deprecated); + if (!oListCollection.deprecated.noVersion.apis.length) { + delete oListCollection.deprecated.noVersion; + } + + /* Experimental */ + oListCollection.experimental = sortList(oListCollection.experimental); + if (!oListCollection.experimental.noVersion.apis.length) { + delete oListCollection.experimental.noVersion; + } + }) + .then(() => { + if (returnOutputFiles) { + filesToReturn[targetFileDeprecated] = JSON.stringify(oListCollection.deprecated); + filesToReturn[targetFileExperimental] = JSON.stringify(oListCollection.experimental); + filesToReturn[targetFileSince] = JSON.stringify(oListCollection.since); + return filesToReturn; + } else { + return Promise.all([ + // write deprecated, experimental and since collections in the respective index files + writeJSON(targetFileDeprecated, oListCollection.deprecated), + writeJSON(targetFileExperimental, oListCollection.experimental), + writeJSON(targetFileSince, oListCollection.since) + ]); + } + }) + .catch((err) => { + log.error("**** failed to create API index for libraries:", err); + throw err; + }); + + return p; + } + + return createOverallIndex(); + +} + +module.exports = createIndexFiles; diff --git a/packages/builder/lib/processors/jsdoc/lib/package.json b/packages/builder/lib/processors/jsdoc/lib/package.json new file mode 100644 index 00000000000..a0df0c86778 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/builder/lib/processors/jsdoc/lib/transformApiJson.js b/packages/builder/lib/processors/jsdoc/lib/transformApiJson.js new file mode 100644 index 00000000000..d608fc541ea --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/transformApiJson.js @@ -0,0 +1,2397 @@ +/** + * Node script to preprocess api.json files for use in the UI5 SDKs. + * + * (c) Copyright 2025 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +"use strict"; +const cheerio = require("cheerio"); +const path = require('path'); +const {TypeParser} = require("./ui5/template/utils/typeParser"); +const log = (function() { + try { + return require("@ui5/logger").getLogger("builder:processors:jsdoc:transformApiJson"); + } catch (error) { + /* eslint-disable no-console */ + return { + info: function info(...msg) { + console.log("[INFO]", ...msg); + }, + error: function error(...msg) { + console.error(...msg); + } + }; + /* eslint-enable no-console */ + } +}()); + +function replaceLastPathSegment(p, replacement) { + // Note: path.join also correctly normalizes any POSIX paths on Windows + return path.join(path.dirname(p), replacement); +} + +function normalizeToUI5GlobalNotation(sModuleName){ + return sModuleName.replace(/\//g, "."); +} + +const typeParser = new TypeParser(); + +/** + * Transforms the api.json as created by the JSDoc build into a pre-processed api.json file suitable for the SDK. + * + * The pre-processing includes formatting of type references, rewriting of links and other time consuming calculations. + * + * @param {string} sInputFile Path of the original api.json file that should be transformed + * @param {string} sOutputFile Path that the transformed api.json file should should be written to + * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information + * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or + * an array of paths of those files + * @param {string} sFAQDir Path to the directory containing the sources for the FAQ section in APiRef + * @returns {Promise} A Promise that resolves after the transformation has been completed + */ +function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sFAQDir, options) { + const fs = options && options.fs || require("fs"); + const returnOutputFiles = options && !!options.returnOutputFiles; + + log.info("Transform API index files for sap.ui.documentation"); + log.info(" original file: " + sInputFile); + log.info(" output file: " + sOutputFile); + log.info(" library file: " + sLibraryFile); + log.info(" dependency dir: " + vDependencyAPIFiles); + log.info(" FAQ src dir: " + sFAQDir); + if (options && options.fs) { + log.info("Using custom fs."); + } + if (returnOutputFiles) { + log.info("Returning output files instead of writing to fs."); + } + log.info(""); + + function resolveTypeParameters(func) { + if ( !func.typeParameters ) { + return func; + } + const replacements = Object.create(null); + let regex; + const _resolve = (str) => + (str && regex ? str.replace(regex, (_,prefix,token,suffix) => prefix + replacements[token] + suffix) : str); + + func.typeParameters.forEach((typeParam) => { + typeParam.type = _resolve(typeParam.type); + replacements[typeParam.name] = typeParam.type || "any"; + regex = new RegExp("(^|\\W)(" + Object.keys(replacements).join("|") + ")(\\W|$)"); + typeParam.default = _resolve(typeParam.default); + }); + + function _resolveParamProperties(param) { + if ( param.parameterProperties ) { + Object.keys(param.parameterProperties).forEach((key) => { + const prop = param.parameterProperties[key]; + prop.type = _resolve(prop.type); + _resolveParamProperties(prop); + }); + } + } + + if ( func.returnValue ) { + func.returnValue.type = _resolve(func.returnValue.type); + } + if ( func.parameters ) { + func.parameters.forEach((param) => { + param.type = _resolve(param.type); + _resolveParamProperties(param); + }); + } + if ( func.throws ) { + func.throws.forEach((ex) => { + ex.type = _resolve(ex.type); + }); + } + return func; + } + + function isUI5Type(sType) { + return !isBuiltInType(sType) && possibleUI5Symbol(sType); + } + + /** + * Returns an object with the parsed custom-type information, namely: + * - types: array of the parsed UI5 types inside the given complex type + * - template: the template string with placeholders for the parsed UI5 types + * + * Examples: + * + * - parseUI5Types("sap.ui.core.ID | sap.ui.core.Control") returns + * { + * template: "${0} | ${1}", + * UI5Types: ["sap.ui.core.ID", "sap.ui.core.Control"] + * } + * + * - parseUI5Types("Array") returns + * { + * template: "Array<${0}>", + * UI5Types: ["sap.ui.core.Control"] + * } + * + * - parseUI5Types("Array") returns + * { + * template: "Array<${0} | string>", // built-in types remain unchanged in the template + * UI5Types: ["sap.ui.core.ID"] + * } + * + * - parseUI5Types("Object") returns + * { + * template: "Object", // built-in types remain unchanged in the template + * UI5Types: ["sap.ui.core.Control"] + * } + * + * - parseUI5Types("string") returns + * { + * template: "string" // skip the types array if empty + * } + * + * - parseUI5Types("sap.ui.core.Control") returns + * { + * UI5Types: ["sap.ui.core.Control"] // skip template if its value is "${0}" (default value) + * } + * @param {string} sComplexType + * @returns {{template=: string, UI5Types=: string[]}} + */ + function parseUI5Types(sComplexType) { + let oParsed; + try { + oParsed = typeParser.parseSimpleTypes(sComplexType, isUI5Type); + } catch (e) { + log.error("Error parsing type: " + sComplexType); + log.error(e); + oParsed = { template: sComplexType }; + } + + const result = {}; + + if (oParsed.template !== '${0}') { // default is '${0}', so omit if not required + result.template = oParsed.template; + } + + if (oParsed.simpleTypes?.length) { // it can be empty if none of the included simple types satisfied the filter function + result.UI5Types = oParsed.simpleTypes; + } + + return result; + } + + function includesIgnoreCase(array, string) { + return array.some((item) => item.toLowerCase() === string.toLowerCase()); + } + + /** + * Check if a type is a built-in type, handling both simple + * and compound types gracefully. + * + * @param {string} type A type to check + * @returns {boolean} true if the type is built-in, otherwise false + */ + function isBuiltInType(type) { + const builtInTypes = formatters._baseTypes; + + // Early return if the type is directly in baseTypes + if (includesIgnoreCase(builtInTypes, type)) { + return true; + } + + // Check if the type is a union type + if (type.includes("|")) { + const unionParts = type.split("|").map((part) => part.trim()); + return unionParts.every((part) => isBuiltInType(part)); + } + + // Handle array notation directly + if (type.endsWith("[]")) { + return builtInTypes.includes(type.slice(0, -2)); + } + + // Predefined regex patterns for reuse + const arrayRegex = /Array<(.+)>/; + const arrayDotRegex = /Array\.<(.+)>/; + const objectRegex = /Object<([^,]+),([^>]+)>/; + const objectDotRegex = /Object\.<([^,]+),([^>]+)>/; + + // Check if the type is a generic Array type + const arrayMatch = arrayRegex.exec(type); + if (arrayMatch) { + const innerType = arrayMatch[1]; + return isBuiltInType(innerType); + } + + const arrayDotMatch = arrayDotRegex.exec(type); + if (arrayDotMatch) { + const innerType = arrayDotMatch[1]; + return isBuiltInType(innerType); + } + + // Check if the type is a generic Object type + const objectMatch = objectRegex.exec(type); + if (objectMatch) { + const innerTypes = [objectMatch[1], objectMatch[2]].map((t) => t.trim()); + return innerTypes.every((innerType) => isBuiltInType(innerType)); + } + + const objectDotMatch = objectDotRegex.exec(type); + if (objectDotMatch) { + const innerTypes = [objectDotMatch[1], objectDotMatch[2]].map((t) => t.trim()); + return innerTypes.every((innerType) => isBuiltInType(innerType)); + } + + // Fallback case: if none of the above matched, return false + return false; + } + + /** + * Heuristically determining if there is a possibility the given input string + * to be a UI5 symbol + * Examples of UI5 symbols: + * -"sap.ui.core.date.UI5Date" + * -"module:sap/ui/core/date/UI5Date" + * @param {string} sName + * @returns {boolean} + */ + function possibleUI5Symbol(sName) { + const ui5SymbolRegex = /^(module:)?sap[/.][a-zA-Z][a-zA-Z0-9/.$]*[a-zA-Z0-9]$/; + return ui5SymbolRegex.test(sName); + } + + /** + * Transforms api.json file + * @param {object} oChainObject chain object + */ + const transformApiJson = function (oChainObject) { + // Function is a copy from: LibraryInfo.js => LibraryInfo.prototype._getActualComponent => "match" inline method + function matchComponent(sModuleName, sPattern) { + sModuleName = sModuleName.toLowerCase(); + sPattern = sPattern.toLowerCase(); + return ( + sModuleName === sPattern + || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0,-1)) === 0 // simple prefix match + || sPattern.match(/\.\*$/) && sModuleName === sPattern.slice(0,-2) // directory pattern also matches directory itself + ); + } + + /** + * Pre-processes the symbols list - creating virtual namespace records and attaching children list to namespace + * records. + * @param {object} symbols list + */ + function preProcessSymbols(symbols) { + // Create treeName and modify module names + symbols.forEach((oSymbol) => { + const sModuleClearName = oSymbol.name.replace(/^module:/, ""); + oSymbol.displayName = sModuleClearName; + oSymbol.treeName = normalizeToUI5GlobalNotation(sModuleClearName); + }); + + // Create missing - virtual namespaces + symbols.forEach((oSymbol) => { + oSymbol.treeName.split(".").forEach((sPart, i, a) => { + const sName = a.slice(0, (i + 1)).join("."); + + if (!symbols.find((o) => o.treeName === sName)) { + symbols.push({ + name: sName, + displayName: sName, + treeName: sName, + lib: oSymbol.lib, + kind: "namespace" + }); + } + }); + }); + + // Discover parents + symbols.forEach((oSymbol) => { + const aParent = oSymbol.treeName.split("."); + + // Extract parent name + aParent.pop(); + const sParent = aParent.join("."); + + // Mark parent + if (symbols.find(({treeName}) => treeName === sParent)) { + oSymbol.parent = sParent; + } + }); + + // Attach children info + symbols.forEach((oSymbol) => { + if (oSymbol.parent) { + const oParent = symbols.find(({treeName}) => treeName === oSymbol.parent); + + if (!oParent.nodes) {oParent.nodes = [];} + + const oNode = { + name: oSymbol.displayName, + description: formatters._preProcessLinksInTextBlock( + extractFirstSentence(oSymbol.description) + ), + href: "api/" + encodeURIComponent(oSymbol.name) + }; + + if (oSymbol.deprecated) { + oNode.deprecated = true; + } + + oParent.nodes.push(oNode); + } + }); + + // Clean list - keep file size down + symbols.forEach((o) => { + delete o.treeName; + delete o.parent; + }); + } + + // Transform to object + const oData = oChainObject.fileData; + + // Attach default component for the library if available + if (oChainObject.defaultComponent) { + oData.defaultComponent = oChainObject.defaultComponent; + } + + formatters._oOwnLibrary = oData; + + // Pre process symbols + preProcessSymbols(oData.symbols); + + // Apply formatter's and modify data as needed + oData.symbols.forEach((oSymbol) => { + + // when the module name starts with the library name, then we apply the default component + if (normalizeToUI5GlobalNotation(oSymbol.displayName).startsWith(oData.library)) { + oSymbol.component = oChainObject.defaultComponent; + } + + // Attach symbol specific component if available (special cases) + // Note: Last hit wins as there may be a more specific component pattern + if (oChainObject.customSymbolComponents) { + Object.keys(oChainObject.customSymbolComponents).forEach((sComponent) => { + if (matchComponent(normalizeToUI5GlobalNotation(oSymbol.displayName), sComponent)) { + oSymbol.component = oChainObject.customSymbolComponents[sComponent]; + } + }); + } + + // Attach symbol sample flag if available + if (oChainObject.entitiesWithSamples) { + oSymbol.hasSample = oChainObject.entitiesWithSamples.indexOf(oSymbol.name) >= 0; + } + + // Apply settings to formatter object - needed until formatter's are rewritten + formatters._sTopicId = oSymbol.name; + formatters._oTopicData = oSymbol; + + // Format Page Title + oSymbol.title = (oSymbol.abstract ? "abstract " : "") + oSymbol.kind + " " + oSymbol.displayName; + oSymbol.subTitle = formatters.formatSubtitle(oSymbol.deprecated); + + // Constructor + if (oSymbol.constructor) { + const oConstructor = resolveTypeParameters(oSymbol.constructor); + + // Description + if (oConstructor.description) { + oConstructor.description = formatters.formatDescription(oConstructor.description); + } + + // References + formatters.modifyReferences(oSymbol, true); + + // Examples + if (oConstructor.examples) { + oConstructor.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + } + + // Code Example string + oConstructor.codeExample = formatters.formatConstructor(oSymbol.name, oConstructor.parameters); + + // Parameters + if (oConstructor.parameters) { + oConstructor.parameters = methods.buildConstructorParameters(oConstructor.parameters); + + const aParameters = oConstructor.parameters; + aParameters.forEach((oParameter) => { + + // Types + oParameter.types = []; + if (oParameter.type) { + oParameter.typeInfo = parseUI5Types(oParameter.type); + // Keep file size in check + delete oParameter.type; + } + + // Default value + oParameter.defaultValue = formatters.formatDefaultValue(oParameter.defaultValue); + + // Description + if (oParameter.description) { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + }); + } + + // Throws + if (oConstructor.throws) { + oConstructor.throws.forEach((oThrows) => { + + // Description + if (oThrows.description) { + oThrows.description = formatters.formatDescription(oThrows.description); + } + + // Exception link enabled + if (oThrows.type) { + oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); + } + + }); + } + } + + // Description + if (oSymbol.description) { + oSymbol.description = formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); + } + + // Deprecated + if (oSymbol.deprecated) { + oSymbol.deprecatedText = formatters.formatDeprecated(oSymbol.deprecated.since, oSymbol.deprecated.text); + // Keep file size in check + delete oSymbol.deprecated; + } + + // Properties + if (oSymbol.properties) { + + // Pre-process properties + if (oSymbol.kind === "typedef") { + oSymbol.properties = methods.buildPropertiesModel(oSymbol.properties); + } + + oSymbol.properties.forEach((oProperty) => { + + // Name + oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); + + // Description + if (oProperty.deprecated) { + oProperty.description = formatters.formatDescription(oProperty.description, + oProperty.deprecated.text, oProperty.deprecated.since); + } else { + oProperty.description = formatters.formatDescription(oProperty.description); + } + + // Examples + if (oProperty.examples) { + oProperty.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + } + + // Type + if (oSymbol.kind !== "enum") { // enum properties don't have an own type + if (oProperty.type) { + oProperty.typeInfo = parseUI5Types(oProperty.type); + // Keep file size in check + delete oProperty.type; + } + } + + // Keep file size in check + if (oProperty.static) { + delete oProperty.static; + } + + delete oProperty.type; + + }); + } + + // UI5 Metadata + if (oSymbol["ui5-metadata"]) { + const oMeta = oSymbol["ui5-metadata"]; + + // Properties + if (oMeta.properties) { + // Sort + oMeta.properties.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.properties.forEach((oProperty) => { + // Name + oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); + + // Type + if (oProperty.type) { + oProperty.typeInfo = parseUI5Types(oProperty.type); + // Keep file size in check + delete oProperty.type; + } + + // Description + oProperty.description = formatters.formatDescriptionSince(oProperty.description, oProperty.since); + + // Default value + oProperty.defaultValue = formatters.formatDefaultValue(oProperty.defaultValue); + + // Deprecated + if (oProperty.deprecated) { + oProperty.deprecatedText = formatters.formatDeprecated(oProperty.deprecated.since, + oProperty.deprecated.text); + + // Keep file size in check + delete oProperty.deprecated; + } + }); + } + + // Aggregations + if (oMeta.aggregations) { + // Sort + oMeta.aggregations.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.aggregations.forEach((oAggregation) => { + // Link Enabled + if (!isBuiltInType(oAggregation.type)) { + oAggregation.linkEnabled = true; + } + + // Description + if (oAggregation.deprecated) { + oAggregation.description = formatters.formatDescription(oAggregation.description); + oAggregation.deprecatedText = formatters.formatDeprecated(oAggregation.deprecated.since, + oAggregation.deprecated.text); + } else { + oAggregation.description = formatters.formatDescriptionSince(oAggregation.description, oAggregation.since); + } + + // Link enabled + oAggregation.linkEnabled = !isBuiltInType(oAggregation.type); + }); + } + + // Associations + + if (oMeta.associations) { + // Sort + oMeta.associations.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.associations.forEach((oAssociation) => { + // Link Enabled + if (!isBuiltInType(oAssociation.type)) { + oAssociation.linkEnabled = true; + } + + // Description + if (oAssociation.deprecated) { + oAssociation.description = formatters.formatDescription(oAssociation.description, + oAssociation.deprecated.text, oAssociation.deprecated.since); + } else { + oAssociation.description = formatters.formatDescriptionSince(oAssociation.description, oAssociation.since); + } + }); + } + + // Events + if (oMeta.events) { + + oMeta.events.forEach((oEvent) => { + const aParams = oEvent.parameters; + + if (aParams) { + Object.keys(aParams).forEach((sParam) => { + const sSince = aParams[sParam].since; + const oDeprecated = aParams[sParam].deprecated; + const oEvtInSymbol = oSymbol.events.find((e) => e.name === oEvent.name); + const oParamInSymbol = oEvtInSymbol && oEvtInSymbol.parameters[0] && + oEvtInSymbol.parameters[0].parameterProperties && + oEvtInSymbol.parameters[0].parameterProperties.getParameters && + oEvtInSymbol.parameters[0].parameterProperties.getParameters.parameterProperties && + oEvtInSymbol.parameters[0].parameterProperties.getParameters.parameterProperties[sParam]; + + if (typeof oParamInSymbol === 'object' && oParamInSymbol !== null) { + if (sSince) { + oParamInSymbol.since = sSince; + } + + if (oDeprecated) { + oParamInSymbol.deprecated = oDeprecated; + } + } + }); + } + }); + + // We don't need event's data from the UI5-metadata for now. Keep file size in check + delete oMeta.events; + } + + // Special Settings + if (oMeta.specialSettings) { + oMeta.specialSettings.forEach((oSetting) => { + + // Link Enabled + if (oSetting.type) { + oSetting.typeInfo = parseUI5Types(oSetting.type); + delete oSetting.type; // Keep file size in check + } + + // Description + if (oSetting.deprecated) { + oSetting.description = formatters.formatDescription(oSetting.description, + oSetting.deprecated.text, oSetting.deprecated.since); + } else { + oSetting.description = formatters.formatDescription(oSetting.description); + } + + }); + } + + // Annotations + if (oMeta.annotations) { + oMeta.annotations.forEach((oAnnotation) => { + + // Description + oAnnotation.description = formatters.formatAnnotationDescription(oAnnotation.description, + oAnnotation.since); + + // Namespace + oAnnotation.namespaceText = oAnnotation.namespace; + oAnnotation.namespace = formatters.formatAnnotationNamespace(oAnnotation.namespace); + + // Target + oAnnotation.target = formatters.formatAnnotationTarget(oAnnotation.target); + + // Applies to + oAnnotation.appliesTo = formatters.formatAnnotationTarget(oAnnotation.appliesTo); + + }); + } + + } + + if (oSymbol.events) { + + // Pre-process events + methods.buildEventsModel(oSymbol.events); + + oSymbol.events.forEach((oEvent) => { + + // Description + if (oEvent.description) { + oEvent.description = formatters.formatDescriptionSince(oEvent.description, oEvent.since); + } + + if (oEvent.references) { + formatters.formatReferencesInDescription(oEvent); + } + + if (oEvent.description) { + oEvent.description = formatters.formatDescription(oEvent.description); + } + + // Deprecated + if (oEvent.deprecated) { + oEvent.deprecatedText = formatters.formatEventDeprecated(oEvent.deprecated.since, + oEvent.deprecated.text); + } + + // Parameters + if (oEvent.parameters && Array.isArray(oEvent.parameters)) { + oEvent.parameters.forEach((oParameter) => { + + if (oParameter.type) { + oParameter.typeInfo = parseUI5Types(oParameter.type); + delete oParameter.type; // Keep file size in check + } + + // Description + if (oParameter.description) { + oParameter.description = formatters.formatDescriptionSince(oParameter.description, oParameter.since); + } + + if (oParameter.references) { + formatters.formatReferencesInDescription(oParameter); + } + + if (oParameter.description) { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + // Deprecated + if (oParameter.deprecated) { + oParameter.deprecatedText = formatters.formatDeprecated(oParameter.deprecated.since, + oParameter.deprecated.text); + } + }); + } + + }); + + } + + // Methods + if (oSymbol.methods) { + + // Pre-process methods + methods.buildMethodsModel(oSymbol.methods); + + oSymbol.methods.forEach((oMethod) => { + + // Name and link + if (oMethod.name) { + oMethod.name = formatters.formatEntityName(oMethod.name, oSymbol.name, oMethod.static); + + // Link + oMethod.href = "api/" + oSymbol.name + + "#methods/" + oMethod.name; + } + + formatters.formatReferencesInDescription(oMethod); + + // Description + if (oMethod.description) { + oMethod.description = formatters.formatDescription(oMethod.description); + } + + // Examples + oMethod.examples?.forEach((oExample) => { + oExample = formatters.formatExample(oExample.caption, oExample.text); + }); + + // Deprecated + if (oMethod.deprecated) { + oMethod.deprecatedText = formatters.formatEventDeprecated(oMethod.deprecated.since, + oMethod.deprecated.text); + } + + // Code example + oMethod.code = formatters.formatMethodCode(oMethod.name, oMethod.parameters, oMethod.returnValue); + + // Parameters + if (oMethod.parameters) { + oMethod.parameters?.forEach((oParameter) => { + + // Default value + oParameter.defaultValue = formatters.formatDefaultValue(oParameter.defaultValue); + + // Description + if (oParameter.deprecated) { + oParameter.description = formatters.formatDescription(oParameter.description, + oParameter.deprecated.text, oParameter.deprecated.since); + } else { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + }); + } + + // Return value + if (oMethod.returnValue) { + + // Description + oMethod.returnValue.description = formatters.formatDescription(oMethod.returnValue.description); + + } + + // Throws + if (oMethod.throws) { + oMethod.throws.forEach((oThrows) => { + + // Description + if (oThrows.description) { + oThrows.description = formatters.formatDescription(oThrows.description); + } + + // Exception link enabled + if (oThrows.type) { + oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); + } + + }); + } + + // Examples + if (oMethod.examples) { + oMethod.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + + } + + + }); + } + + // Formatting namespaces, functions and enums, which may contain examples + // or references with links to process them similar to methods and constructors of classes + + if (oSymbol.kind !== "class") { + + if (oSymbol.examples) { + oSymbol.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + } + + if (oSymbol.references) { + formatters.modifyReferences(oSymbol); + formatters.formatReferencesInDescription(oSymbol); + } + + // Description + if (oSymbol.description) { + oSymbol.description = formatters.formatDescription(oSymbol.description); + } + } + }); + + oChainObject.parsedData = oData; + + return oChainObject; + }; + + function extractFirstSentence(sHTML) { + const rRegExp = /^.*?[\.!\?](?:\s|$)/; + const aMatch = rRegExp.exec(sHTML); + + return aMatch !== null ? aMatch[0] : sHTML; + } + + function getDependencyLibraryFilesList(oChainObject) { + // if vDependencyAPIFiles is an array, it contains the file paths of api.json files + if ( Array.isArray(vDependencyAPIFiles) ) { + return oChainObject; + } + + // otherwise, it names a directory that has to be scanned for the files + return new Promise((oResolve) => { + fs.readdir(vDependencyAPIFiles, function (oError, aItems) { + if (!oError && aItems && aItems.length) { + const aFiles = []; + aItems.forEach((sItem) => { + aFiles.push(path.join(vDependencyAPIFiles, sItem)); + }); + oChainObject.aDependentLibraryFiles = aFiles; + } + oResolve(oChainObject); // We don't fail if no dependency library files are available + }); + }); + } + + function loadDependencyLibraryFiles (oChainObject) { + if (!oChainObject.aDependentLibraryFiles) { + return oChainObject; + } + const aPromises = []; + oChainObject.aDependentLibraryFiles.forEach((sFile) => { + aPromises.push(new Promise((oResolve) => { + fs.readFile(sFile, 'utf8', (oError, oData) => { + oResolve(oError ? false : oData); + }); + })); + }); + return Promise.all(aPromises).then((aValues) => { + const oDependentAPIs = {}; + + aValues.forEach((sData) => { + let oData; + + try { + oData = JSON.parse(sData); + } catch (e) { + // Silence possible dependency library invalid json errors as they are not critical + // and we should not fail BCP: 1870560058 + } + + // OpenUI5 build specific - own library can be listed as dependency library also. + // In this case we don't add it to the dependency list to skip double iteration. + if (oData && oChainObject.fileData.library !== oData.library) { + oDependentAPIs[oData.library] = oData.symbols; + } + }); + + oChainObject.oDependentAPIs = oDependentAPIs; + return oChainObject; + }); + } + + /** + * Check for existence of FAQ data + * (FAQ data must be defined as *.md files in the sFAQDir) + * and add a boolean flag in case it exists + * + * @param oChainObject chain object + */ + function addFlagsForFAQData(oChainObject) { + if (!sFAQDir) { + return oChainObject; + } + const slibName = oChainObject.fileData.library; + oChainObject.fileData.symbols.forEach(function(symbol) { + const sfile = symbol.name.replace(slibName, "").replace(/[.]/g, "/") + ".md"; + if (fs.existsSync(path.join(sFAQDir, sfile))) { + symbol.hasFAQ = true; + } + }); + return oChainObject; + } + + /** + * Create api.json from parsed data + * @param oChainObject chain object + */ + function createApiRefApiJson(oChainObject) { + if (returnOutputFiles) { + // If requested, return data instead of writing to FS (required by UI5 CLI/UI5 Builder) + return JSON.stringify(oChainObject.parsedData); + } + const sOutputDir = path.dirname(oChainObject.outputFile); + + // Create dir if it does not exist + if (!fs.existsSync(sOutputDir)) { + fs.mkdirSync(sOutputDir); + } + + // Write result to file + fs.writeFileSync(oChainObject.outputFile, JSON.stringify(oChainObject.parsedData) /* Transform back to string */, 'utf8'); + } + + /** + * Load .library file + * @param oChainObject chain return object + * @returns {Promise} library file promise + */ + function getLibraryPromise(oChainObject) { + return new Promise(function(oResolve) { + fs.readFile(oChainObject.libraryFile, 'utf8', (oError, oData) => { + oChainObject.libraryFileData = oData; + oResolve(oChainObject); + }); + }); + } + + /** + * Extracts components list and docuindex.json relative path from .library file data + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function extractComponentAndDocuindexUrl(oChainObject) { + oChainObject.modules = []; + + if (oChainObject.libraryFileData) { + const $ = cheerio.load(oChainObject.libraryFileData, { + ignoreWhitespace: true, + xmlMode: true, + lowerCaseTags: false + }); + + // Extract documentation URL + oChainObject.docuPath = $("appData documentation").attr("indexUrl"); + + // Extract components + $("ownership > component").each((i, oComponent) => { + + if (oComponent.children) { + if (oComponent.children.length === 1) { + oChainObject.defaultComponent = $(oComponent).text(); + } else { + const sCurrentComponentName = $(oComponent).find("name").text(); + const aCurrentModules = []; + $(oComponent).find("module").each((a, oC) => { + aCurrentModules.push($(oC).text().replace(/\//g, ".")); + }); + + oChainObject.modules.push({ + componentName: sCurrentComponentName, + modules: aCurrentModules + }); + } + } + + }); + + } + + return oChainObject; + } + + /** + * Adds to the passed object custom symbol component map generated from the extracted components list + * to be easily searchable later + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function flattenComponents(oChainObject) { + if (oChainObject.modules && oChainObject.modules.length > 0) { + oChainObject.customSymbolComponents = {}; + oChainObject.modules.forEach((oComponent) => { + const sCurrentComponent = oComponent.componentName; + oComponent.modules.forEach((sModule) => { + oChainObject.customSymbolComponents[sModule] = sCurrentComponent; + }); + }); + } + + return oChainObject; + } + + /** + * Adds to the passed object array with entities which have explored samples + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function extractSamplesFromDocuIndex(oChainObject) { + // If we have not extracted docuPath we return early + if (!oChainObject.docuPath) { + return oChainObject; + } + return new Promise(function(oResolve) { + // Join .library path with relative docuindex.json path + let sPath = path.join(path.dirname(oChainObject.libraryFile), oChainObject.docuPath); + // Normalize path to resolve relative path + sPath = path.normalize(sPath); + + const {sources} = options; + let {librarySrcDir, libraryTestDir} = options; + + if (Array.isArray(sources) && typeof librarySrcDir === "string" && typeof libraryTestDir === "string") { + + // Using path.join to normalize POSIX paths to Windows paths on Windows + librarySrcDir = path.join(librarySrcDir); + libraryTestDir = path.join(libraryTestDir); + + /** + * Calculate prefix to check + * + * Example 1: + * - sSource: /path/to/project/src + * - sLibrarySrcDir: src + * + * Prefix: /path/to/project/test-resources + * + * Example 2: + * - sSource: /path/to/project/src/main/js + * - sLibrarySrcDir: src/main/js + * + * Prefix: /path/to/project/src/main/test-resources + */ + const librarySrcDirWithTestResources = replaceLastPathSegment(librarySrcDir, "test-resources"); + + for (const sourcePath of sources) { + /** + * Replace prefix with file system path + * + * Example 1: + * - sPath: /path/to/project/test-resources/sap/test/demokit/docuindex.json + * + * New sPath: /path/to/project/test/sap/test/demokit/docuindex.json + * + * Example 2: + * - sPath: /path/to/project/src/main/test-resources/sap/test/demokit/docuindex.json + * + * New sPath: /path/to/project/src/main/test/sap/test/demokit/docuindex.json + */ + if (!sourcePath.endsWith(librarySrcDir)) { + continue; + } + const libraryDir = sourcePath.substring(0, sourcePath.lastIndexOf(librarySrcDir) - 1); + const prefix = path.join(libraryDir, librarySrcDirWithTestResources); + if (sPath.startsWith(prefix)) { + sPath = path.join(libraryDir, libraryTestDir, sPath.substring(prefix.length)); + break; + } + } + } + + fs.readFile(sPath, 'utf8', (oError, oFileData) => { + if (!oError) { + oFileData = JSON.parse(oFileData); + if (oFileData.explored && oFileData.explored.entities && oFileData.explored.entities.length > 0) { + oChainObject.entitiesWithSamples = []; + oFileData.explored.entities.forEach((oEntity) => { + oChainObject.entitiesWithSamples.push(oEntity.id); + }); + } + } + // We always resolve as this data is not mandatory + oResolve(oChainObject); + }); + + }); + } + + /** + * Load api.json file + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function getAPIJSONPromise(oChainObject) { + return new Promise(function(oResolve, oReject) { + fs.readFile(sInputFile, 'utf8', (oError, sFileData) => { + if (oError) { + oReject(oError); + } else { + oChainObject.fileData = JSON.parse(sFileData); + oResolve(oChainObject); + } + }); + }); + } + + /** + * ===================================================================================================================== + * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and mocking and + * code can be significantly improved + * ===================================================================================================================== + */ + const formatters = { + _sTopicId: "", + _oTopicData: {}, + _baseTypes: [ + "sap.ui.core.any", + "sap.ui.core.object", + "sap.ui.core.function", + "sap.ui.core.number", + "sap.ui.core.float", + "sap.ui.core.int", + "sap.ui.core.boolean", + "sap.ui.core.string", + "sap.ui.core.void", + "null", + "any", + "Error", + "array", + "element", + "Element", + "HTMLElement", + "Node", + "Attr", + "Date", + "DomRef", + "jQuery", + "jQuery.promise", + "jQuery.event", + "QUnit.Assert", + "object", + "Object", + "function", + "float", + "int", + "boolean", + "string", + "number", + "map", + "promise", + "Promise", + "document", + "Document", + "Touch", + "TouchList", + "undefined", + "this", + "Blob", + "RegExp", + "void", + "ArrayBuffer", + "[object Object]" + ], + ANNOTATIONS_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html', + ANNOTATIONS_NAMESPACE_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/', + + /** + * Adds "deprecated" information if such exists to the header area + * @param deprecated - object containing information about deprecation + * @returns {string} - the deprecated text to display + */ + formatSubtitle: function (deprecated) { + var result = ""; + + if (deprecated) { + result += "Deprecated in version: " + deprecated.since; + } + + return result; + }, + + /** + * Formats the target and applies to texts of annotations + * @param target - the array of texts to be formatted + * @returns string - the formatted text + */ + formatAnnotationTarget: function (target) { + var result = ""; + + if (target) { + target.forEach(function (element) { + result += element + '
'; + }); + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the namespace of annotations + * @param namespace - the namespace to be formatted + * @returns string - the formatted text + */ + formatAnnotationNamespace: function (namespace) { + var aNamespaceParts = namespace.split("."), + result, target, text; + + if (aNamespaceParts[0] === "Org" && aNamespaceParts[1] === "OData") { + target = this.ANNOTATIONS_NAMESPACE_LINK + namespace + ".xml"; + text = namespace; + result = this.handleExternalUrl(target, text); + } else { + result = namespace; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the description of annotations + * @param description - the description of the annotation + * @param since - the since version information of the annotation + * @returns string - the formatted description + */ + formatAnnotationDescription: function (description, since) { + var result = description || ""; + + result += '
For more information, see ' + this.handleExternalUrl(this.ANNOTATIONS_LINK, "OData v4 Annotations"); + + if (since) { + result += '

Since: ' + since + '.'; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + formatExceptionLink: function (linkText) { + linkText = linkText || ''; + return linkText.indexOf('sap.ui.') !== -1; + }, + + formatMethodCode: function (sName, aParams, aReturnValue) { + var result = '
' + sName + '(';
+
+			if (aParams && aParams.length > 0) {
+				/* We consider only root level parameters so we get rid of all that are not on the root level */
+				aParams = aParams.filter((oElem) => {
+					return oElem.depth === undefined;
+				});
+				aParams.forEach(function (element, index, array) {
+					result += element.name;
+
+					if (element.optional) {
+						result += '?';
+					}
+
+					if (index < array.length - 1) {
+						result += ', ';
+					}
+				});
+			}
+
+			result += ') : ';
+
+			if (aReturnValue) {
+				result += aReturnValue.type;
+			} else {
+				result += 'void';
+			}
+
+			result += "
"; + + return result; + }, + + /** + * Formats method deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @returns {string} formatted deprecation message + */ + formatMethodDeprecated: function (sSince, sDescription) { + return this.formatDeprecated(sSince, sDescription, "methods"); + }, + + /** + * Formats event deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @returns {string} formatted deprecation message + */ + formatEventDeprecated: function (sSince, sDescription) { + return this.formatDeprecated(sSince, sDescription, "events"); + }, + + /** + * Formats the description of control properties + * @param description - the description of the property + * @param since - the since version information of the property + * @returns string - the formatted description + */ + formatDescriptionSince: function (description, since) { + var result = description || ""; + + if (since) { + result += '

Since: ' + since + '.'; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the default value of the property as a string. + * @param defaultValue - the default value of the property + * @returns string - The default value of the property formatted as a string. + */ + formatDefaultValue: function (defaultValue) { + var sReturn; + + switch (defaultValue) { + case null: + case undefined: + sReturn = ''; + break; + case '': + sReturn = 'empty string'; + break; + default: + if (Object.keys(defaultValue).length === 0 && defaultValue.constructor === Object) { + sReturn = 'empty object'; + } else { + sReturn = defaultValue; + } + } + + return Array.isArray(sReturn) ? sReturn.join(', ') : sReturn; + }, + + /** + * Formats the constructor of the class + * @param name + * @param params + * @returns string - The code needed to create an object of that class + */ + formatConstructor: function (name, params) { + var result = '
new ';
+
+			if (name) {
+				result += name + '(';
+			}
+
+			if (params) {
+				params.forEach(function (element, index, array) {
+					result += element.name;
+
+					if (element.optional) {
+						result += '?';
+					}
+
+					if (index < array.length - 1) {
+						result += ', ';
+					}
+				});
+			}
+
+			if (name) {
+				result += ')
'; + } + + return result; + }, + + formatExample: function (sCaption, sText) { + return this.formatDescription( + ["Example: ", + sCaption, + "
",
+					sText,
+					"
"].join("") + ); + }, + + /** + * Formats the name of a property or a method depending on if it's static or not + * @param sName {string} - Name + * @param sClassName {string} - Name of the class + * @param bStatic {boolean} - If it's static + * @returns {string} - Formatted name + */ + formatEntityName: function (sName, sClassName, bStatic) { + return (bStatic === true) ? sClassName + "." + sName : sName; + }, + + JSDocUtil: function () { + + var rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; + + // Local mocked methods + var escapeRegExp = function escapeRegExp(sString) { + return sString.replace(rEscapeRegExp, "\\$&"); + }; + + function defaultLinkFormatter(target, text) { + return "" + (text || target) + ""; + } + + function format(src, options) { + + options = options || {}; + var beforeParagraph = options.beforeParagraph === undefined ? '

' : options.beforeParagraph; + var afterParagraph = options.afterParagraph === undefined ? '

' : options.afterParagraph; + var beforeFirstParagraph = options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; + var afterLastParagraph = options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; + var linkFormatter = typeof options.linkFormatter === 'function' ? options.linkFormatter : defaultLinkFormatter; + + /* + * regexp to recognize important places in the text + * + * Capturing groups of the RegExp: + * group 1: begin of a pre block + * group 2: end of a pre block + * group 3: begin of a header, implicitly ends a paragraph + * group 4: end of a header, implicitly starts a new paragraph + * group 5: target portion of an inline @link tag + * group 6: (optional) text portion of an inline link tag + * group 7: an empty line which implicitly starts a new paragraph + * + * [--
 block -] [---- some header ----] [---- an inline [@link ...} tag ----] [---------- an empty line ---------]  */
+				var r = /(
)|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
+				var inpre = false;
+
+				src = src || '';
+				linkFormatter = linkFormatter || defaultLinkFormatter;
+
+				src = beforeFirstParagraph + src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
+					if ( pre ) {
+						inpre = true;
+					} else if ( endpre ) {
+						inpre = false;
+					} else if ( header ) {
+						if ( !inpre ) {
+							return afterParagraph + match;
+						}
+					} else if ( endheader ) {
+						if ( !inpre ) {
+							return match + beforeParagraph;
+						}
+					} else if ( emptyline ) {
+						if ( !inpre ) {
+							return afterParagraph + beforeParagraph;
+						}
+					} else if ( linkTarget ) {
+						if ( !inpre ) {
+							return linkFormatter(linkTarget, linkText);
+						}
+					}
+					return match;
+				}) + afterLastParagraph;
+
+				// remove empty paragraphs
+				if (beforeParagraph !== "" && afterParagraph !== "") {
+					src = src.replace(new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
+				}
+
+				return src;
+			}
+
+			return {
+				formatTextBlock: format
+			};
+
+		},
+
+		formatUrlToLink: function(sTarget, sText, bSAPHosted){
+			return `${sText}
+			`;
+		},
+
+		handleExternalUrl: function (sTarget, sText) {
+			// Check if the external domain is SAP hosted
+			const bSAPHosted = /^https?:\/\/([\w.]*\.)?(?:sap|hana\.ondemand|sapfioritrial)\.com/.test(sTarget);
+			return this.formatUrlToLink(sTarget, sText, bSAPHosted);
+		},
+
+		/**
+		 * Discover possible target type by looking at symbols from own and depending libraries
+		 * @param {string} target
+		 * @param {object} self
+		 * @param {object} ownLibrary
+		 * @param {object} dependencyLibs
+		 * @param {boolean} module
+		 * @returns {string}
+		 */
+		createLinkFromTargetType: function ({className, methodName, target, self, ownLibrary, dependencyLibs, text}) {
+			let sResult;
+
+			function searchInSymbol(oSymbol) {
+				function findProperty(oEntity, sName, sTarget) {
+					if (!oEntity || !oEntity.properties) {
+						return;
+					}
+					return oEntity.properties.find(({name}) => name === sName || name === sTarget);
+				}
+
+				function findMethod(oEntity, sName, sTarget) {
+					if (!oEntity || !oEntity.methods) {
+						return;
+					}
+					return oEntity.methods.find(({name}) => name === sName || name === sTarget);
+				}
+
+				function findEvent(oEntity, sName) {
+					if (!oEntity || !oEntity.events) {
+						return;
+					}
+					return oEntity.events.find(({name}) => name === sName);
+				}
+
+				function findAnnotation(oEntity, sName) {
+					if (!oEntity || !oEntity.annotations) {
+						return;
+					}
+					return oEntity.annotations.find(({name}) => name === sName);
+				}
+
+				if (oSymbol.name === target) {
+					sResult = this.createLink({
+						name: oSymbol.name,
+						text: text
+					});
+					return true;
+				}
+				if (oSymbol.name === className) {
+					const oProperty = findProperty(oSymbol, methodName, target);
+					if (oProperty) {
+						sResult = this.createLink({
+							name: className,
+							text: text
+						});
+						return true;
+					}
+					const oMethod = findMethod(oSymbol, methodName, target);
+					if (oMethod) {
+						sResult = this.createLink({
+							name: oMethod.static ? target : oMethod.name,
+							type: "methods",
+							text: text,
+							className: className
+						});
+						return true;
+					}
+
+					const oEvent = findEvent(oSymbol, methodName);
+					if (oEvent) {
+						sResult = this.createLink({
+							name: oEvent.name,
+							type: "events",
+							text: text,
+							className: className
+						});
+						return true;
+					}
+
+					const oAnnotation = findAnnotation(oSymbol, methodName);
+					if (oAnnotation) {
+						sResult = this.createLink({
+							name: oAnnotation.name,
+							type: "annotations",
+							text: text,
+							className: className
+						});
+						return true;
+					}
+				}
+
+				return false;
+			}
+
+			// Self link
+			if (self.name === target) {
+				return this.createLink({
+					name: target,
+					text: text
+				});
+			}
+
+			// Own methods search
+			if (self.name === className && self.methods) {
+				const oResult = self.methods.find(({name}) => name === methodName);
+				if (oResult) {
+					return this.createLink({
+						name: oResult.static ? [self.name, oResult.name].join(".") : oResult.name,
+						type: "methods",
+						className: className,
+						text: text
+					});
+				}
+			}
+
+			// Local library symbols
+			ownLibrary.symbols.find((oSymbol) => {
+				return searchInSymbol.call(this, oSymbol);
+			});
+
+			if (sResult) {return sResult;}
+
+			// Dependent library symbols
+			if (dependencyLibs) {
+				Object.keys(dependencyLibs).find((sLib) => {
+					if (sLib === target) {
+						sResult = this.createLink({
+							name: sLib,
+							text: text
+						});
+						return true;
+					}
+					const oLib = dependencyLibs[sLib];
+					return oLib && oLib.find((oSymbol) => {
+						return searchInSymbol.call(this, oSymbol);
+					});
+				});
+			}
+
+			return sResult;
+		},
+
+		/**
+		 * Creates a html link
+		 * @param {string} name
+		 * @param {string} type
+		 * @param {string} className
+		 * @param {string} [text=name] by default if no text is provided the name will be used
+		 * @param {boolean} [local=false]
+		 * @param {string} [hrefAppend=""]
+		 * @returns {string} link
+		 */
+		createLink: function ({name, type, className, text = name, hrefAppend = ""}) {
+			let sLink;
+
+			// handling module's
+			if (className !== undefined && (/^module:/.test(name) || /^module:/.test(className))) {
+				name = name.replace(/^module:/, "");
+			}
+
+			// Build the link
+			sLink = type ? `${className}#${type}/${name}` : name;
+
+			if (hrefAppend) {
+				sLink += hrefAppend;
+			}
+
+			return `${text}`;
+		},
+
+		/**
+		 * Pre-process links in text block
+		 * @param {string} sText text block
+		 * @param {boolean} bSkipParagraphs skip paragraphs
+		 * @returns {string} processed text block
+		 * @private
+		 */
+		_preProcessLinksInTextBlock: function (sText, bSkipParagraphs) {
+			const oSelf = this._oTopicData,
+				oOwnLibrary = this._oOwnLibrary,
+				oDependencyLibs = oChainObject.oDependentAPIs,
+				oOptions = {
+					linkFormatter: function (sTarget, sText) {
+						let aMatch;
+
+						// keep the full target in the fallback text
+						sText = sText || sTarget;
+
+						// If the link has a protocol, do not modify, but open in a new window
+						if (/:\/\//.test(sTarget)) {
+							return this.handleExternalUrl(sTarget, sText);
+						}
+
+						// topic:xxx Topic
+						aMatch = sTarget.match(/^topic:(\w{32}(?:#\w*)?(?:\/\w*)?)$/);
+						if (aMatch) {
+							return '' + sText + '';
+						}
+
+						// demo:xxx Demo, open the demonstration page in a new window
+						aMatch = sTarget.match(/^demo:([a-zA-Z0-9\/.]*)$/);
+						if (aMatch) {
+							return this.formatUrlToLink("test-resources/" + aMatch[1], sText, true);
+						}
+
+						// sap.x.Xxx.prototype.xxx - In case of prototype we have a link to method
+						aMatch = sTarget.match(/([a-zA-Z0-9.$_]+?)\.prototype\.([a-zA-Z0-9.$_]+)$/);
+						if (aMatch) {
+							return this.createLink({
+								name: aMatch[2],
+								type: "methods",
+								className: aMatch[1],
+								text: sText
+							});
+						}
+
+						// Heuristics: Extend is always a static method
+						// sap.x.Xxx.extend
+						// module:sap/x/Xxx.extend
+						aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)\.extend$/);
+						if (aMatch) {
+							const [, sModule, sClass] = aMatch;
+							return this.createLink({
+								name: sTarget.replace(/^module:/, ""),
+								type: "methods",
+								className: (sModule ? sModule : "") + sClass,
+								text: sText
+							});
+						}
+
+						// Constructor links are handled in special manner by the SDK
+						// sap.x.Xxx.constructor
+						// sap.x.Xxx#constructor
+						// module:sap/x/Xxx.constructor
+						// #constructor
+						aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)?[\.#]constructor$/i);
+						if (aMatch) {
+							const [, sModule, sClass] = aMatch;
+
+							let sName;
+							if (sClass) {
+								sName = (sModule ? sModule : "") + sClass;
+							} else {
+								sName = oSelf.name;
+							}
+
+							return this.createLink({
+								name: sName,
+								hrefAppend: "#constructor",
+								text: sText
+							});
+						}
+
+						// #.setText - local static method
+						// #setText - local instance method
+						// #.setText.from - local nested method
+						aMatch = sTarget.match(/^#(\.)?([a-zA-Z0-9.$_]+)$/);
+						if (aMatch) {
+							return this.createLink({
+								name: aMatch[1] ? `${oSelf.name}.${aMatch[2]}` : aMatch[2],
+								type: "methods",
+								className: oSelf.name,
+								text: sText
+							});
+						}
+
+						// #annotation:TextArrangement - local annotation
+						aMatch = sTarget.match(/^#annotation:([a-zA-Z0-9$_]+)$/);
+						if (aMatch) {
+							return this.createLink({
+								name: aMatch[1],
+								type: "annotations",
+								className: oSelf.name,
+								text: sText
+							});
+						}
+						// Annotation links
+						// sap.ui.comp.smartfield.SmartField#annotation:TextArrangement
+						// sap.ui.comp.smartfield.SmartField.annotation:TextArrangement
+						// module:sap/ui/comp/smartfield/SmartField.annotation:TextArrangement
+						// module:sap/ui/comp/smartfield/SmartField#annotation:TextArrangement
+						aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)[.#]annotation:([a-zA-Z0-9$_]+)$/);
+						if (aMatch) {
+							const [, sModule, sClass, sAnnotation] = aMatch;
+							return this.createLink({
+								name: sAnnotation,
+								type: "annotations",
+								className: (sModule ? sModule : "") + sClass,
+								text: sText
+							});
+						}
+
+						// #event:press - local event
+						aMatch = sTarget.match(/^#event:([a-zA-Z0-9$_]+)$/);
+						if (aMatch) {
+							return this.createLink({
+								name: aMatch[1],
+								type: "events",
+								className: oSelf.name,
+								text: sText
+							});
+						}
+						// Event links
+						// sap.m.Button#event:press
+						// sap.m.Button.event:press
+						// module:sap/m/Button.event:press
+						// module:sap/m/Button#event:press
+						aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)[.#]event:([a-zA-Z0-9$_]+)$/);
+						if (aMatch) {
+							const [, sModule, sClass, sEvent] = aMatch;
+							return this.createLink({
+								name: sEvent,
+								type: "events",
+								className: (sModule ? sModule : "") + sClass,
+								text: sText
+							});
+						}
+
+						// sap.m.Button#setText - instance method
+						// module:sap/m/Button#setText
+						aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+)#([a-zA-Z0-9.$_]+)$/);
+						if (aMatch) {
+							const [, sModule, sClass, sMethod] = aMatch;
+							return this.createLink({
+								name: sMethod,
+								type: "methods",
+								className: (sModule ? sModule : "") + sClass,
+								text: sText
+							});
+						}
+
+						// Unresolved type - try to discover target type
+						// sap.x.Xxx.xxx
+						// module:sap/x/Xxx.xxx
+						if (/^(?:module:)?([a-zA-Z0-9.$_\/]+?)\.([a-zA-Z0-9$_]+)$/.test(sTarget)) {
+							const [,sClass, sName] = sTarget.match(/^((?:module:)?[a-zA-Z0-9.$_\/]+?)\.([a-zA-Z0-9$_]+)$/);
+							const sResult = this.createLinkFromTargetType({
+									className: sClass,
+									methodName: sName,
+									target: sTarget,
+									self: oSelf,
+									ownLibrary: oOwnLibrary,
+									dependencyLibs: oDependencyLibs,
+									text: sText
+								});
+							if (sResult) {
+								return sResult;
+							}
+						}
+
+						// Possible nested functions discovery - currently we do this only for regular symbols
+						const aTarget = sTarget.split(".");
+						if (aTarget.length >= 3) {
+							const sResult = this.createLinkFromTargetType({
+								methodName: aTarget.splice(-2).join("."),
+								className: aTarget.join("."),
+								target: sTarget,
+								self: oSelf,
+								ownLibrary: oOwnLibrary,
+								dependencyLibs: oDependencyLibs,
+								text: sText
+							});
+							if (sResult) {
+								return sResult;
+							}
+						}
+
+						// Possible forward reference - we will treat them as symbol link
+						return this.createLink({
+							name: sTarget,
+							text: sText
+						});
+
+					}.bind(this)
+				};
+
+			if (bSkipParagraphs) {
+				oOptions.beforeParagraph = "";
+				oOptions.afterParagraph = "";
+			}
+
+			return this.JSDocUtil().formatTextBlock(sText, oOptions);
+		},
+
+		/**
+		 * Formatter for Overview section
+		 * @param {string} sDescription - Class about description
+		 * @param {array} aReferences - References
+		 * @returns {string} - formatted text block
+		 */
+		formatOverviewDescription: function (sDescription, aReferences) {
+			var iLen,
+				i;
+
+			// format references
+			if (aReferences && aReferences.length > 0) {
+				sDescription += "

Documentation links:
    "; + + iLen = aReferences.length; + for (i = 0; i < iLen; i++) { + // We treat references as links but as they may not be defined as such we enforce it if needed + if (/{@link.*}/.test(aReferences[i])) { + sDescription += "
  • " + aReferences[i] + "
  • "; + } else { + sDescription += "
  • {@link " + aReferences[i] + "}
  • "; + + } + } + + sDescription += "
"; + } + + // Calling formatDescription so it could handle further formatting + return this.formatDescription(sDescription); + }, + + /** + * Formats the description of the property + * @param description - the description of the property + * @param deprecatedText - the text explaining this property is deprecated + * @param deprecatedSince - the version when this property was deprecated + * @returns string - the formatted description + */ + formatDescription: function (description, deprecatedText, deprecatedSince) { + if (!description && !deprecatedText && !deprecatedSince) { + return ""; + } + + var result = description || ""; + + if (deprecatedSince || deprecatedText) { + // Note: sapUiDocumentationDeprecated - transformed to sapUiDeprecated to keep json file size low + result += "
"; + + result += this.formatDeprecated(deprecatedSince, deprecatedText); + + result += "
"; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the entity deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @param {string} sEntityType string representation of entity type + * @returns {string} formatted deprecation message + */ + formatDeprecated: function (sSince, sDescription, sEntityType) { + var aResult; + + // Build deprecation message + // Note: there may be no since or no description text available + aResult = ["Deprecated"]; + if (sSince) { + aResult.push(" as of version " + sSince); + } + if (sDescription) { + // Evaluate code blocks - Handle ... pattern + sDescription = sDescription.replace(/(\S+)<\/code>/gi, function (sMatch, sCodeEntity) { + return ['', sCodeEntity, ''].join(""); + } + ); + + // Evaluate links in the deprecation description + aResult.push(". " + this._preProcessLinksInTextBlock(sDescription, true)); + } + + return aResult.join(""); + }, + + /** + * Pre-process and modify references + * @param {object} oSymbol control data object which will be modified + * @private + */ + modifyReferences: function (oSymbol, bCalledOnConstructor) { + var bHeaderDocuLinkFound = false, + bUXGuidelinesLinkFound = false, + aReferences = [], + entity = bCalledOnConstructor ? oSymbol.constructor.references : oSymbol.references; + + const UX_GUIDELINES_BASE_URL = "https://experience.sap.com/fiori-design-web/"; + + if (entity && entity.length > 0) { + entity.forEach(function (sReference) { + var aParts; + + // Docu link - For the header we take into account only the first link that matches one of the patterns + if (!bHeaderDocuLinkFound) { + + // Handled patterns: + // * topic:59a0e11712e84a648bb990a1dba76bc7 + // * {@link topic:59a0e11712e84a648bb990a1dba76bc7} + // * {@link topic:59a0e11712e84a648bb990a1dba76bc7 Link text} + aParts = sReference.match(/^{@link\s+topic:(\w{32})(\s.+)?}$|^topic:(\w{32})$/); + + if (aParts) { + if (aParts[3]) { + // Link is of type topic:GUID + oSymbol.docuLink = aParts[3]; + oSymbol.docuLinkText = oSymbol.basename; + } else if (aParts[1]) { + // Link of type {@link topic:GUID} or {@link topic:GUID Link text} + oSymbol.docuLink = aParts[1]; + oSymbol.docuLinkText = aParts[2] ? aParts[2] : oSymbol.basename; + } + bHeaderDocuLinkFound = true; + return; + } + } + + // Fiori link - Handled patterns: + // * fiori:flexible-column-layout + // * fiori:/flexible-column-layout/ + // * fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ + // * {@link fiori:flexible-column-layout} + // * {@link fiori:/flexible-column-layout/} + // * {@link fiori:/flexible-column-layout/ Flexible Column Layout} + // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/} + // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout} + aParts = sReference.match(/^(?:{@link\s)?fiori:(?:https:\/\/experience\.sap\.com\/fiori-design-web\/)?\/?(\S+\b)\/?\s?(.*[^\s}])?}?$/); + + if (aParts) { + const [, sTarget, sTargetName] = aParts; + + if (bCalledOnConstructor && !bUXGuidelinesLinkFound) { + // Extract first found UX Guidelines link as primary + oSymbol.uxGuidelinesLink = UX_GUIDELINES_BASE_URL + sTarget; + oSymbol.uxGuidelinesLinkText = sTargetName ? sTargetName : oSymbol.basename; + bUXGuidelinesLinkFound = true; + return; + } else { + // BCP: 1870155880 - Every consecutive "fiori:" link should be handled as a normal link + sReference = "{@link " + UX_GUIDELINES_BASE_URL + sTarget + (sTargetName ? " " + sTargetName : "") + "}"; + } + } + + aReferences.push(sReference); + }); + } + if (bCalledOnConstructor) { + oSymbol.constructor.references = aReferences; + } else { + oSymbol.references = aReferences; + } + }, + + /** + * Manage References, to apply as an unordered list in the description + * @param {object} oEntity control data object which will be modified + * @private + */ + formatReferencesInDescription: function(oEntity) { + if (oEntity.references && Array.isArray(oEntity.references)) { + oEntity.references = oEntity.references.map((sReference) => { + if (/{@link.*}/.test(sReference)) { + return "
  • " + sReference + "
  • "; + } else { + return "
  • {@link " + sReference + "}
  • "; + + } + }); + if (!oEntity.description) { + // If there is no method description - references should be the first line of it + oEntity.description = ''; + } else { + oEntity.description += '

    '; + } + oEntity.description += `References:
      ${oEntity.references.join("")}
    `; + } + } + }; + + /* Methods direct copy from API Detail */ + const methods = { + + /** + * Adjusts methods info so that it can be easily displayed in a table + * @param aMethods - the methods array initially coming from the server + */ + buildMethodsModel: function (aMethods) { + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { + if (oParameter.parameterProperties) { + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty]; + + oProperty.depth = iDepth; + + // Handle types + if (oProperty.type) { + oProperty.typeInfo = parseUI5Types(oProperty.type); + // Keep file size in check + delete oProperty.type; + } + + // Phone name - available only for parameters + oProperty.phoneName = [aPhoneName.join("."), oProperty.name].join("."); + + // Add property to parameter array as we need a simple structure + aParameters.push(oProperty); + + // Handle child parameterProperties + fnExtractParameterProperties(oProperty, aParameters, (iDepth + 1), aPhoneName.concat([oProperty.name])); + + // Keep file size in check + delete oProperty.type; + }); + + // Keep file size in check + delete oParameter.parameterProperties; + } + }; + aMethods.forEach(function (oMethod) { + oMethod = resolveTypeParameters(oMethod); + + // New array to hold modified parameters + var aParameters = []; + + // Handle parameters + if (oMethod.parameters) { + oMethod.parameters.forEach(function (oParameter) { + if (oParameter.type) { + oParameter.typeInfo = parseUI5Types(oParameter.type); + // Keep file size in check + delete oParameter.type; + } + + // Add the parameter before the properties + aParameters.push(oParameter); + + // Handle Parameter Properties + // Note: We flatten the structure + fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]); + + }); + + // Override the old data + oMethod.parameters = aParameters; + } + + // Handle return values + if (oMethod.returnValue && oMethod.returnValue.type) { + // Handle types + oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type); + } + + }); + }, + + /** + * Adjusts events info so that it can be easily displayed in a table + * @param {Array} aEvents - the events array initially coming from the server + */ + buildEventsModel: function (aEvents) { + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { + if (oParameter.parameterProperties) { + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty], + sPhoneTypeSuffix; + + oProperty.depth = iDepth; + + // Phone name - available only for parameters + sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : ""; + oProperty.phoneName = [aPhoneName.join("."), (oProperty.name + sPhoneTypeSuffix)].join("."); + + // Add property to parameter array as we need a simple structure + aParameters.push(oProperty); + + // Handle child parameterProperties + fnExtractParameterProperties(oProperty, aParameters, (iDepth + 1), + aPhoneName.concat([oProperty.name + sPhoneTypeSuffix])); + }); + + // Keep file size in check + delete oParameter.parameterProperties; + } + }; + aEvents.forEach(function (aEvents) { + // New array to hold modified parameters + var aParameters = []; + + // Handle parameters + if (aEvents.parameters) { + aEvents.parameters.forEach(function (oParameter) { + // Add the parameter before the properties + aParameters.push(oParameter); + + // Handle Parameter Properties + // Note: We flatten the structure + fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]); + }); + + // Override the old data + aEvents.parameters = aParameters; + } + }); + }, + + /** + * Adjusts constructor parameters info so that it can be easily displayed in a table + * @param {Array} aParameters - the events array initially coming from the server + */ + buildConstructorParameters: function (aParameters) { + // New array to hold modified parameters + var aNodes = [], + processNode = function (oNode, sPhoneName, iDepth, aNodes) { + // Handle phone name + oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name; + + // Depth + oNode.depth = iDepth; + + // Add to array + aNodes.push(oNode); + + // Handle nesting + if (oNode.parameterProperties) { + Object.keys(oNode.parameterProperties).forEach(function (sNode) { + processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes); + }); + } + + delete oNode.parameterProperties; + }; + + aParameters.forEach(function (oParameter) { + // Handle Parameter Properties + // Note: We flatten the structure + processNode(oParameter, undefined, 0, aNodes); + }); + + return aNodes; + }, + + buildPropertiesModel: function(aOrigProperties) { + const aProperties = []; + + const fnEmbedNestedProperties = (oParentProperty, iDepth, aPhoneName) => { + if (oParentProperty.properties) { + Object.values(oParentProperty.properties).forEach((oProperty) => { + + oProperty.depth = iDepth; + + // Phone name - available only for parameters + oProperty.phoneName = [aPhoneName.join("."), oProperty.name].join("."); + + // Description (no deprecation etc. possible in JSDoc) + oProperty.description = formatters.formatDescription(oProperty.description); + + // Add property to parameter array as we need a simple structure + aProperties.push(oProperty); + + // Handle child parameterProperties + fnEmbedNestedProperties(oProperty, (iDepth + 1), aPhoneName.concat([oProperty.name])); + + }); + + // Keep file size in check + delete oParentProperty.properties; + } + }; + + aOrigProperties.forEach((oProperty) => { + aProperties.push(oProperty); + fnEmbedNestedProperties(oProperty, 1, [oProperty.name]); + }); + + return aProperties; + }, + + oLibsData: {}, + + }; + + // Create the chain object + const oChainObject = { + inputFile: sInputFile, + outputFile: sOutputFile, + libraryFile: sLibraryFile, + aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null + }; + + // Start the work here + const p = getLibraryPromise(oChainObject) + .then(extractComponentAndDocuindexUrl) + .then(flattenComponents) + .then(extractSamplesFromDocuIndex) + .then(getDependencyLibraryFilesList) + .then(getAPIJSONPromise) + .then(loadDependencyLibraryFiles) + .then(transformApiJson) + .then(addFlagsForFAQData) + .then(createApiRefApiJson); + return p; + +} + +module.exports = transformer; + diff --git a/packages/builder/lib/processors/jsdoc/lib/ui5/plugin.js b/packages/builder/lib/processors/jsdoc/lib/ui5/plugin.js new file mode 100644 index 00000000000..361ed32ff59 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/ui5/plugin.js @@ -0,0 +1,3502 @@ +/* + * JSDoc3 plugin for UI5 documentation generation. + * + * (c) Copyright 2025 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +/* global env */ + +'use strict'; + +/** + * UI5 plugin for JSDoc3 (3.3.0-alpha5) + * + * The plugin adds the following SAPUI5 specific tag definitions to JSDoc3 + * + * disclaimer + * + * experimental + * + * final + * + * hideconstructor + * + * interface + * + * implements + * + * ui5-omissible-param + * + * ui5-restricted + * + * + * + * It furthermore listens to the following JSDoc3 events to implement additional functionality + * + * parseBegin + * to create short names for all file that are to be parsed + * + * fileBegin + * to write some line to the log (kind of a progress indicator) + * + * jsdocCommentFound + * to pre-process comments, empty lines are used as paragraph markers + * a default visibility is added, legacy tag combinations used in JSdoc2 are converted to JSDoc3 conventions + * + * newDoclet + * + * parseComplete + * merge collected class info, DataType info and enum values into doclets + * + * processingComplete + * remove undocumented/ignored/private doclets or duplicate doclets + * + * Last but not least, it implements an astNodeVisitor to detect UI5 specific "extend" calls and to create + * documentation for the properties, aggregations etc. that are created with the "extend" call. + * + * @module plugins/sapui5-jsdoc + */ + +/* imports */ +const Syntax = { + // Those are currently not in the syntax.js file. + ChainExpression: "ChainExpression", + OptionalMemberExpression: "OptionalMemberExpression", + OptionalCallExpression: "OptionalCallExpression", + + ...require("jsdoc/src/syntax").Syntax, +}; +const Doclet = require('jsdoc/doclet').Doclet; +const fs = require('jsdoc/fs'); +const path = require('jsdoc/path'); +const logger = require('jsdoc/util/logger'); +const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || env.opts.sapui5 || {}; +const escope = require("escope"); +const {isSemVer} = require("./template/utils/versionUtil"); + +/* ---- logging ---- */ + +// let pendingMessageHeader; + +function msgHeader(str) { + // pendingMessageHeader = str; +} + +const debug = logger.debug.bind(logger); +const info = logger.info.bind(logger); +const warning = logger.warn.bind(logger); +const error = logger.error.bind(logger); + +/* errors that might fail the build in future */ +function future(msg) { + if ( logger.getLevel() >= logger.LEVELS.WARN ) { + const args = [...arguments]; + args[0] = "FUTURE: " + args[0] + " (future error, ignored for now)"; + /* eslint-disable no-console */ + console.warn(...args); + /* eslint-disable no-console */ + } +} + +/* ---- global vars---- */ + +/** + * Potential path prefixes. + * + * Will be determined in the handler for the parseBegin event + */ +let pathPrefixes = []; + +/** + * Prefixes of the UI5 unified resource name for the source files is NOT part of the file name. + * (e.g. when a common root namespaces has been omitted from the folder structure). + * + * The prefix will be prepended to all resource names. + */ +let resourceNamePrefixes = []; + +/** + * A UI5 specific unique Id for all doclets. + */ +let docletUid = 0; + +let currentProgram; + +// Scope Manager +let scopeManager; + +/** + * Information about the current module. + * + * The info object is created in the 'fileBegin' event handler and the 'resource' and 'module' properties + * are derived from the filename provided by the event. The derived information is only correct, when the + * resource name prefix is known for the directory from which a source is loaded (prefixes can be configured + * via sapui5.resourceNamePrefixes, for UI5 libraries it is empty by default). + * + * During AST visiting, the 'name' property and the 'localeNames' map will be filled. + * 'name' will be the name of the class defined by the module (assuming that there is only one). + * 'localNames' will contain information objects for each parameter of an AMD Factory function and for + * all shortcut variables that are defined top-level in the module factory function (e.g. something like + * var ButtonDesign = coreLibrary.ButtonDesign; ). + * An info object for a local name either can have a 'value' property (simple, constant value) or it can + * have a 'module' and optionally a 'path' value. In that case, the local name represents an AMD + * module import or a shortcut derived from such an import. + * + * See {@link getResolvedObjectName} how the knowledge about locale names is used. + * + * @type {{name:string,resource:string,module:string,localName:Object}} + */ +let currentModule; + +let currentSource; + +/** + * Cached UI5 metadata for encountered UI5 classes. + * + * The metadata is collected from the 'metadata' property of 'extend' calls. It is stored + * in this map keyed by the name of the class (as defined in the first parameter of the extend call). + * Only after all files have been parsed, the collected information can be associated with the + * corresponding JSDoc doclet (e.g. with the class documentation). + */ +const classInfos = Object.create(null); + +/** + * Map of enum value objects keyed by a unqiue enum ID. + * + * When the AST visitor detects an object literal that might be an enum, it cannot easily determine + * the name of the enum. Therefore, the collected enum values are stored in this map keyed by a + * unique ID derived from the key set of the (potential) enum (ID = sorted key set, joined with '|'). + * + * In the parseComplete phase, the found values are merged into enum symbols (as detected by JSDoc). + */ +const enumValues = Object.create(null); + +/** + * + */ +const typeInfos = Object.create(null); + +/** + * Cached designtime info for encountered sources. + * + * The designtime information is collected only for files named '*.designtime.js'. + * It is stored in this map keyed by the corresponding module name (e.g. 'sap/m/designtime/Button.designtime'). + * Only after all files have been parsed, the collected information can be associated with runtime metadata + * that refers to that designtime module name. + */ +const designtimeInfos = Object.create(null); + +/* ---- private functions ---- */ + +function ui5data(doclet) { + // eslint-disable-next-line no-return-assign + return doclet.__ui5 || (doclet.__ui5 = { id: ++docletUid }); +} + +//---- path handling --------------------------------------------------------- + +function ensureEndingSlash(path) { + path = path || ''; + return path && path.slice(-1) !== '/' ? path + '/' : path; +} + +function getRelativePath(filename) { + let relative = path.resolve(filename); + for ( let i = 0; i < pathPrefixes.length; i++ ) { + if ( relative.indexOf(pathPrefixes[i]) === 0 ) { + relative = relative.slice(pathPrefixes[i].length); + break; + } + } + return relative.replace(/\\/g, '/'); +} + +function getResourceName(filename) { + let resource = path.resolve(filename); + for ( let i = 0; i < pathPrefixes.length; i++ ) { + if ( resource.indexOf(pathPrefixes[i]) === 0 ) { + resource = resourceNamePrefixes[i] + resource.slice(pathPrefixes[i].length); + break; + } + } + return resource.replace(/\\/g, '/'); +} + +function getModuleName(resource) { + return resource.replace(/\.js$/, ''); +} + +/* + * resolves relative AMD module identifiers relative to a given base name + */ +function resolveModuleName(base, name) { + let stack = base.split('/'); + stack.pop(); + name.split('/').forEach((segment, i) => { + if ( segment === '..' ) { + stack.pop(); + } else if ( segment === '.' ) { + // ignore + } else { + if ( i === 0 ) { + stack = []; + } + stack.push(segment); + } + }); + return stack.join('/'); +} + +// ---- AMD handling + +function analyzeModuleDefinition(node) { + const args = node.arguments; + let arg = 0; + if ( arg < args.length && isStringLiteral(args[arg]) ) { + currentModule.name = convertValue(args[arg]); + warning(`module explicitly defined a module name '${currentModule.name}'`); + const resourceModuleName = getModuleName(currentModule.resource); + if (currentModule.name !== resourceModuleName) { + warning(`explicitly defined module name '${currentModule.name}' ` + + `differs from resource name '${resourceModuleName}'`); + } + arg++; + } + if ( arg < args.length && args[arg].type === Syntax.ArrayExpression ) { + currentModule.dependencies = convertValue(args[arg], "string[]"); + arg++; + } + if ( arg < args.length && + [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(args[arg].type)) { + + currentModule.factory = args[arg]; + arg++; + } + + if ( currentModule.dependencies && currentModule.factory ) { + for ( let i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) { + let names = []; + + if ( [Syntax.ObjectPattern, Syntax.ArrayPattern].includes(currentModule.factory.params[i].type) ) { // ObjectPattern/ArrayPattern means destructuring of the parameter of a function + names = resolveObjectPatternChain(currentModule.factory.params[i], null, []); + + } else if (currentModule.factory.params[i].type === Syntax.Identifier) { // simple Identifier + names = [{original: currentModule.factory.params[i].name}]; + } + + /*eslint-disable no-loop-func */ + names.forEach((name) => { + const module = resolveModuleName(currentModule.module, currentModule.dependencies[i]); + debug(` import ${name.renamed || name.original} from '${module}'`); + + currentModule.localNames[name.renamed || name.original] = { + module: module, + ...(name.path ? {path: name.path} : {}) + }; + }); + /*eslint-enable no-loop-func */ + } + } + if ( currentModule.factory ) { + collectShortcuts(currentModule.factory); + } +} + +/** + * Searches the body of the given factory for variable declarations that can be evaluated statically, + * either because they refer to known AMD module imports (e.g. shortcut variables) + * or because they have a (design time) constant value. + * + * @param {ASTNode} factory AST node of a factory function whose body that shall be searched for shortcuts + */ +function collectShortcuts(factory) { + const body = factory.body; + + function checkAssignment(name, valueNode) { + valueNode = resolvePotentialWrapperExpression(valueNode); + + if ( valueNode.type === Syntax.Literal ) { + currentModule.localNames[name] = { + value: valueNode.value, + raw: valueNode.raw + }; + debug("compile time constant found ", name, valueNode.value); + } else if ( isMemberExpression(valueNode) ) { + const _import = getLeftmostName(valueNode); + const local = _import && currentModule.localNames[_import]; + const objectName = getObjectName(valueNode); + if ( objectName && typeof local === 'object' && local.module ) { + currentModule.localNames[name] = { + module: local.module, + path: objectName.split('.').slice(1).join('.') // TODO chaining if local has path + }; + debug(` found local shortcut: ${name} ${currentModule.localNames[name]}`); + } + } else if ( isRequireSyncCall(valueNode) || isProbingRequireCall(valueNode) ) { + if ( valueNode.arguments[0] && isStringLiteral(valueNode.arguments[0]) ) { + currentModule.localNames[name] = { + module: convertValue(valueNode.arguments[0]) + // no (or empty) path + }; + debug(` found local import: ${name} = ${valueNode.callee.property.name}('${valueNode.arguments[0].value}')`); + } + } else if ( isExtendCall(valueNode) ) { + currentModule.localNames[name] = { + "class": valueNode.arguments[0].value + // no (or empty) path + }; + debug(` found local class definition: ${name} = .extend('${valueNode.arguments[0].value}', ...)`); + } + } + + if ( body.type === Syntax.BlockStatement || isArrowFuncExpression(factory) ) { + const itemsResolver = function ( stmt ) { + // console.log(stmt); + if ( stmt.type === Syntax.FunctionDeclaration ) { + if ( stmt.id && stmt.id.type === Syntax.Identifier && stmt.loc && stmt.loc.start ) { + const loc = stmt.loc.start.line + ":" + stmt.loc.start.column; + currentModule.localNamesByLoc[loc] = + currentModule.localNames[stmt.id.name] = { + "function": stmt + }; + } + } else if ( stmt.type === Syntax.VariableDeclaration ) { + stmt.declarations.forEach((decl) => { + if ( decl.init ) { + checkAssignment(decl.id.name, decl.init); + } + }); + } else if ( stmt.type === Syntax.ExpressionStatement + && stmt.expression.type === Syntax.AssignmentExpression + && stmt.expression.left.type === Syntax.Identifier ) { + checkAssignment(stmt.expression.left.name, stmt.expression.right); + } else if ( isReturningNode(stmt) ) { + const stmtArgument = isArrowFuncExpression(stmt) + ? stmt.body + : resolvePotentialWrapperExpression(stmt).argument; + + if ( stmtArgument && stmtArgument.type === Syntax.Identifier ) { + currentModule.defaultExport = stmtArgument.name; + } else if ( stmtArgument && stmtArgument.type === Syntax.ClassExpression && stmtArgument.id && stmtArgument.id.type === Syntax.Identifier ) { + debug(` found default export class definition: return class '${stmtArgument.id.name}'`); + currentModule.defaultExportClass = stmtArgument.id.name; + } else if ( stmtArgument && isExtendCall(stmtArgument) ) { + debug(` found default export class definition: return .extend('${stmtArgument.arguments[0].value}', ...)`); + currentModule.defaultExportClass = convertValue(stmtArgument.arguments[0]); + } + } + }; + + if (isArrowFuncExpression(factory)) { + itemsResolver(factory); + } else { + body.body.forEach(itemsResolver); + } + } + + if ( currentModule.defaultExport && currentModule.localNames[currentModule.defaultExport] ) { + currentModule.localNames[currentModule.defaultExport].export = ""; + } +} + +function findLocalDeclaration(lineno, columnno) { + return currentModule.localNamesByLoc[lineno + ":" + columnno]; +} + +// ---- text handling --------------------------------------------------------- + +const rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i; +const mSingular = {'children' : -3, 'ies' : 'y', 'ves' : 'f', 'oes' : -2, 'ses' : -2, 'ches' : -2, 'shes' : -2, 'xes' : -2, 's' : -1 }; + +function guessSingularName(sPluralName) { + return sPluralName.replace(rPlural, ($, sPlural) => { + const vRepl = mSingular[sPlural.toLowerCase()]; + return typeof vRepl === "string" ? vRepl : sPlural.slice(0, vRepl); + }); +} + +function getPropertyKey(prop) { + if ( prop.type === Syntax.SpreadElement ) { + return undefined; + } else if ( prop.key.type === Syntax.Identifier && prop.computed !== true ) { + return prop.key.name; + } else if ( prop.key.type === Syntax.Literal ) { + return String(prop.key.value); + } + return undefined; +} + +/** + * Creates a map of property values from an AST 'object literal' node. + * + * The values in the map are again AST 'property' nodes (representing key/value pairs). + * It would be more convenient to just return the values, but the property node is needed + * to find the corresponding (preceding) documentation comment. + * + * If a defaultKey is given and if the node is not an object literal + * but another simple type literal, the value is treated as a shortcut for + *
    + *   {
    + *     [defaultKey]: node.value
    + *   }
    + * 
    + * This is used in ManagedObjectMetadata to allow a simpler declaration of properties by + * specifying a type name only. + * + * @param {ASTNode} node AST node for an object literal or simple literal + * @param {string} [defaultKey=undefined] A default key to use for simple values + * @returns {Map} Map of AST nodes of type 'Property', keyed by their property name + */ +function createPropertyMap(node, defaultKey) { + + let result; + + if ( node != null ) { + + // if, instead of an object literal only a literal is given and there is a defaultKey, then wrap the literal in a map + if ( node.type === Syntax.Literal && defaultKey != null ) { + result = {}; + result[defaultKey] = { type: Syntax.Property, value: node }; + return result; + } + + if ( node.type !== Syntax.ObjectExpression ) { + // something went wrong, it's not an object literal + warning(`not an object literal: ${node.type}: ${node.value}`); + // console.log(node.toSource()); + return undefined; + } + + // invariant: node.type === Syntax.ObjectExpression + result = {}; + for (let i = 0; i < node.properties.length; i++) { + const prop = node.properties[i]; + //console.log("objectproperty " + prop.type); + const name = getPropertyKey(prop); + //console.log("objectproperty " + prop.type + ":" + name); + result[name] = prop; + } + } + return result; +} + +/** + * Resolves potential wrapper expressions like: ChainExpression, AwaitExpression, etc. + * @param {Node} node the node to unwrap + * @returns {Node} the resolved node + */ +function resolvePotentialWrapperExpression(node) { + switch (node && node.type) { + case Syntax.ChainExpression: + return node.expression; + + case Syntax.AwaitExpression: + return node.argument; + + case Syntax.ExpressionStatement: + if ( node.expression.type === Syntax.YieldExpression ) { + return node.expression.argument.type === Syntax.UpdateExpression + ? node.expression.argument + : node.expression; + } + // fall through + default: + return node; + } +} + +/** + * Navigates in the given tree of AST nodes (node) along the given path of property names. + * Any reached ChainExpression wrappers are skipped (ignored). + * + * @param {Node} rootNode Root of a tree of MemberExpressison nodes + * @param {string} path Path to navigate along + * @returns {Node} The target node at the end of the path or undefined + */ +function stripChainWrappers(rootNode, path) { + const strip = (node) => + (node && node.type === Syntax.ChainExpression ? node.expression : node); + + let curNode = strip(rootNode); + + if (path) { + const chunks = path.split("."); + + while (chunks.length) { + const name = chunks.shift(); + curNode = curNode && strip(curNode[name]); + } + } + + return curNode; +} + +function isTemplateLiteralWithoutExpression(node) { + return ( + node && + node.type === Syntax.TemplateLiteral && + node.expressions && node.expressions.length === 0 && + node.quasis && node.quasis.length === 1 + ); +} + +/** + * Checks whether a node is Literal or TemplateLiteral without an expression + * + * @param {Node} node AST node to check + * @returns {boolean} Whether the given AST node represents a constant string + */ +function isStringLiteral(node) { + return ( + (node && node.type === Syntax.Literal && typeof node.value === "string") + || isTemplateLiteralWithoutExpression(node) + ); +} + +function isMemberExpression(node) { + return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); +} + +function isCaleeMemberExpression(node) { + return ( + node && + [Syntax.CallExpression, Syntax.OptionalCallExpression].includes(node.type) && + isMemberExpression(node.callee) + ); +} + +function isExtendCall(node) { + node = stripChainWrappers(node); + + return ( + node + && isCaleeMemberExpression(node) + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'extend' + && node.arguments.length >= 2 + && isStringLiteral(node.arguments[0]) + && node.arguments[1].type === Syntax.ObjectExpression + ); + +} + +function isArrowFuncExpression(node) { + return ( + node && + node.type === Syntax.ArrowFunctionExpression && + node.expression === true + ); +} + +/** + * Checks whether the node is of a "returning" type. + * + * @param {Node} node A statement node + * @returns {boolean} Whether the node is a return stmt or a yield expression statement + */ +function isReturningNode(node) { + return (node && node.type === Syntax.ReturnStatement) + || (node && node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.YieldExpression) + || isArrowFuncExpression(node); +} + +function isSapUiDefineCall(node) { + + return ( + stripChainWrappers(node) + && isCaleeMemberExpression(stripChainWrappers(node)) + && isMemberExpression(stripChainWrappers(node, "callee.object")) + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'define' + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' + ); + +} + +function isCreateDataTypeCall(node) { + node = stripChainWrappers(node); + + return ( + node + && isCaleeMemberExpression(node) + && getResolvedObjectName(stripChainWrappers(node, "callee.object")) === "sap.ui.base.DataType" + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'createType' + ); +} + +function isRequireSyncCall(node) { + node = stripChainWrappers(node); + + return ( + node + && isCaleeMemberExpression(node) + && isMemberExpression( stripChainWrappers(node, "callee.object") ) + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'requireSync' + ); +} + +function isProbingRequireCall(node) { + node = stripChainWrappers(node); + + return ( + node + && isCaleeMemberExpression(node) + && isMemberExpression( stripChainWrappers(node, "callee.object") ) + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'require' + && node.arguments.length === 1 + && isStringLiteral(node.arguments[0]) + ); +} + +function isPotentialEnum(node) { + if ( node == null || node.type !== Syntax.ObjectExpression ) { + return false; + } + return node.properties.every((prop) => isCompileTimeConstant(prop.value)); +} + +// ---- ES6+ Destructuring --------------------------------------------------------- + +/** + * Resolves (nested) Object/ArrayPattern nodes and builds a "path" + * + * @param {Node} valueNode + * @param {Node} keyNode + * @param {Array} keyChain + * @returns Array>> + */ +function resolveObjectPatternChain (valueNode, keyNode, keyChain) { + let chainSequence = []; + + if (valueNode && valueNode.type === Syntax.ObjectPattern) { + for (let i = 0; i < valueNode.properties.length; i++) { + chainSequence = chainSequence.concat( + resolveObjectPatternChain( + valueNode.properties[i].value, + valueNode.properties[i].key, + [...keyChain, valueNode.properties[i].key.name] ) + ); + } + } else if (valueNode && valueNode.type === Syntax.ArrayPattern) { + for (let i = 0; i < valueNode.elements.length; i++) { + chainSequence = chainSequence.concat( + resolveObjectPatternChain( + valueNode.elements[i], + valueNode.elements[i], + [...keyChain, String(i)] + ) + ); + } + } else { + + const result = { original: keyNode.name, path: keyChain.join(".") }; + + if (keyNode.name !== valueNode.name) { + // Renaming + result.renamed = valueNode.name; + } + + chainSequence.push(result); + } + + return chainSequence; +} + +/** + * Tries to resolve an ENUM, regardless where it is defined and being destructured. + * + * @param {Node} node + * @param {string} type + * @returns {{value:any, raw: any} | undefined} + */ +function resolvePotentialEnum(node, type) { + let value = resolveFullyQuantifiedName(node); + + if ( value.startsWith(type + ".") ) { + // starts with fully qualified enum name -> cut off name + value = value.slice(type.length + 1); + return { + value: value, + raw: value + }; + } + return undefined; +} + +/** + * Returns the `Node` of the destructured argument of a (arrow) function. + * + * @param {Definition|ParameterDefinition} varDefinition + * @returns {Node | undefined} + */ +function getFuncArgumentDestructNode(varDefinition) { + if ( + [ + Syntax.ArrowFunctionExpression, + Syntax.FunctionDeclaration, + Syntax.FunctionExpression, + ].includes(varDefinition.node.type) + ) { + return varDefinition.node.params[varDefinition.index]; + } + + return undefined; +} + +/** + * Checks whether a variable has been destructured. + * + * @param {Variable} variable + * @returns {boolean} whether `variable` uses destructuring. + */ +function isVarDestructuring(variable) { + const defNode = + variable && + variable.defs && + ( getFuncArgumentDestructNode(variable.defs[0]) // (arrow) function argument + || variable.defs[0].node.id ); // variable definition + + return defNode != null && [Syntax.ObjectPattern, Syntax.ArrayPattern].includes( defNode.type ); +} + +/** + * Checks whether a var has been renamed while destructuring i.e. {A: b} = SomeObject + * + * @param {Variable} variable + * @returns {Object} + */ +function checkVarRenaming(variable) { + // variable.defs[0].node.id.type === Syntax.ObjectPattern -> Renaming + // variable.defs[0].node.id.properties[0].key.name === variable.name; // Original + // variable.defs[0].node.id.properties[0].value.name === variable.name; // Renamed + + // If variable.defs (Variable definitions within the source code) are more 1, then we'd not be able to + // determine which defintion to use. For example: + // function doSomething({a}, b) { + // console.log(a); + // var { c : a } = { b }; + // console.log(a); + // } + // doSomething({a:42}, 5); + // + // So, we'd not able to analyze which "a" to which console.log to map + if ( + !variable + || !variable.defs + || variable.defs.length !== 1 + ) { + return null; + } + + const varDefinition = variable.defs[0]; + const defNode = getFuncArgumentDestructNode(varDefinition) // (arrow) function argument + || varDefinition.node.id; // variable definition + + return resolveObjectPatternChain(defNode, null, []).find( + ({ original, renamed }) => + renamed === variable.name || original === variable.name + ); +} + +/** + * Builds the fully quantified name when there's a destructuring of a variable + * + * @param {Node} node + * @returns {string} + */ +function resolveFullyQuantifiedName(node) { + // The missing part is on the left side. The right side is clear. + // We would eiter ways resolve to the same leftmost token. + let leftMostName = getLeftmostName(node); + let originalName = getObjectName(node) || ""; + const currentScope = getEnclosingVariableScope(node); + + if (!currentScope) { + return ""; + } + + while (leftMostName) { + const curVar = currentScope.set.get(leftMostName); + + if (!curVar) { + break; + } + + if ( !isVarDestructuring(curVar) ) { + // Not a destructuring + return getResolvedName(originalName, leftMostName); + } + + const potentialRenaming = checkVarRenaming(curVar); + if (potentialRenaming) { + let renamedChunks = originalName.split("."); + renamedChunks = getResolvedName( originalName, renamedChunks[0] ).split("."); + + // when getResolvedName() was not able to resolve the renaming of a variable + // with currentModule.localNames[].path i.e. in "chained" destructuring, where + // the variable is not within localNames registry + if ( potentialRenaming.renamed === renamedChunks[0] ) { // Checks for direct renaming + renamedChunks[0] = potentialRenaming.original; + } + // Considers the 'path' if it differs from the original name i.e. there's some namespace before it + if ( potentialRenaming.original === renamedChunks[0] && potentialRenaming.path !== potentialRenaming.original ) { + renamedChunks[0] = potentialRenaming.path; + } + + originalName = renamedChunks.join("."); + } + + const writeExpr = curVar.references.filter((ref) => ref.writeExpr); + + // The same case as variable.defs- we're not able to determine the correct chain in case of multiple writeExpr + if (writeExpr.length !== 1) { + // writeExpr.length === 0 means an function argument and then we need + // just to return the already build originalName + return writeExpr.length === 0 ? originalName : ""; + } + + const writeExprNode = writeExpr[0].writeExpr; + + // determine from write expression how to replace the leftmost name + if (writeExprNode.type === Syntax.MemberExpression) { + // replacement is a qualified name (its leftmost part will be resolved in the next round) + leftMostName = getResolvedObjectName(writeExprNode); + + } else if (writeExprNode.type === Syntax.Identifier) { + // leftMostName was an alias only + leftMostName = writeExprNode.name; + + } else { + leftMostName = ""; + } + + if (leftMostName) { + originalName = leftMostName + "." + originalName; + // determine new leftMostName to resolve next + leftMostName = leftMostName.split(".")[0]; + } + } + + return originalName; +} + +/** + * Gets enclosing scope + * + * @param {Node} node + * @returns {Scope} + */ + function getEnclosingVariableScope (node) { + // Get to the nearest upper scope + let nearestScopeableNode = node; + let nearestScope = scopeManager.acquire(nearestScopeableNode); + while ( + !nearestScope && + nearestScopeableNode && + nearestScopeableNode.parent + ) { + nearestScopeableNode = nearestScopeableNode.parent; + nearestScope = scopeManager.acquire(nearestScopeableNode); + } + + // FunctionExpression with a name hold the value of the name in its scope. + // So, we need to unwrap to get to the real scope with var definitions + return nearestScope.functionExpressionScope + ? nearestScope.childScopes[0] + : nearestScope; +} + +function isCompileTimeConstant(node) { + return node && node.type === Syntax.Literal; +} + +function getObjectName(node) { + if ( isMemberExpression(node) && !node.computed && node.property.type === Syntax.Identifier ) { + const prefix = getObjectName(node.object); + return prefix ? prefix + "." + node.property.name : null; + } else if ( node.type === Syntax.Identifier ) { + return /* scope[node.name] ? scope[node.name] : */ node.name; + } else { + return null; + } +} + +/* + * Checks whether the node is a qualified name (a.b.c) and if so, + * returns the leftmost identifier 'a'. + */ +function getLeftmostName(node) { + while ( isMemberExpression(node) ) { + node = node.object; + } + if ( node.type === Syntax.Identifier ) { + return node.name; + } + return undefined; +} + +function getResolvedObjectName(node) { + const name = getObjectName(node); + const _import = getLeftmostName(node); + + return getResolvedName(name, _import); +} + +function getResolvedName(name, _import) { + const local = _import && currentModule.localNames[_import]; + if ( name && local && (local.class || local.module) ) { + let resolvedName; + if ( local.class ) { + resolvedName = local.class; + } else { + resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, ""); + if ( local.path ) { + resolvedName = resolvedName + "." + local.path; + } + } + if ( name.indexOf('.') > 0 ) { + resolvedName = resolvedName + name.slice(name.indexOf('.')); + } + debug(`resolved ${name} to ${resolvedName}`); + return resolvedName; + } + return name; +} + +/* + * Analyzes the given AST node that represents a value and returns an object + * with two properties: + * - 'value' contains the runtime representation of the value (e.g. a number or a string) + * - 'raw' contains a source code representation of the value (always string) + * + * @param {ASTNode} node Node to analyze + * @param {string} [type] A type name that might help to analyze the value + * @param {string} [propertyName] Name of the property for which the anylsis i done, only used for logging + * @returns {{value:any,raw:string}} An object with a runtime and a source code representation of the value + */ +function convertValueWithRaw(node, type, propertyName) { + + let value; + + if ( node.type === Syntax.Literal ) { + + // 'string' or number or true or false + return { + value: node.value, + raw: node.raw + }; + + } else if ( node.type === Syntax.UnaryExpression + && node.prefix + && node.argument.type === Syntax.Literal + && typeof node.argument.value === 'number' + && ( node.operator === '-' || node.operator === '+' )) { + + // -n or +n + value = node.argument.value; + return { + value: node.operator === '-' ? -value : value, + raw: node.operator + node.argument.raw + }; + + } else if ( isMemberExpression(node) && type ) { + + // enum value (a.b.c) + const potentialEnum = resolvePotentialEnum(node, type); + + if ( potentialEnum ) { + return potentialEnum; + } else { + value = getResolvedObjectName(node); + warning(`did not understand default value '${value}'${propertyName ? " of property '" + propertyName + "'" : ""}, falling back to source`); + let raw = value; + if ( currentSource && node.range ) { + raw = currentSource.slice( node.range[0], node.range[1] ); + } + return { + value: value, + raw: raw + }; + } + + } else if ( node.type === Syntax.Identifier ) { + if ( node.name === 'undefined') { + return { + value: undefined, + raw: node.name + }; + } + const local = currentModule.localNames[node.name]; + if ( typeof local === 'object' && 'value' in local ) { + // a locally defined constant + // TODO check type + return { + value: local.value, + raw: local.raw + }; + } + + // This could be an ENUM which has been destructured up to the enum value part. In that case the node.type === Syntax.Identifier + // For example, the `Solid` const in the following snippet: + // sap.ui.define(["sap/m/library"], ( { BackgroundDesign } ) => { + // const { Solid } = BackgroundDesign; + const potentialEnum = resolvePotentialEnum(node, type); + if ( potentialEnum ) { + return potentialEnum; + } + } else if ( node.type === Syntax.ArrayExpression ) { + + if ( node.elements.length === 0 ) { + // empty array literal + return { + value: [], + raw: "[]" + }; + } + + if ( type && type.slice(-2) === "[]" ) { + const componentType = type.slice(0, -2); + const array = node.elements.map((elem) => convertValueWithRaw(elem, componentType, propertyName)); + return { + value: array.map((value) => value.value), + raw: "[" + array.map((value) => value.raw).join(", ") + "]" + }; + } + + } else if ( node.type === Syntax.ObjectExpression ) { + + if ( node.properties.length === 0 && (type === 'object' || type === 'any') ) { + return { + value: {}, + raw: "{}" + }; + } + + } else if ( isTemplateLiteralWithoutExpression(node) ) { + const value = node.quasis[0].value || {}; + + return { + value: value.cooked, + raw: value.raw, + }; + } + + value = "...see text or source"; + warning(`cannot understand default value${ + propertyName ? " of property '" + propertyName + "'" : "" + } (type='${node.type}', source='${node.toString()}'), falling back to '${value}'`); + + return { + value: value, + raw: value + }; +} + +function convertValue(node, type, propertyName, includeRaw) { + return convertValueWithRaw(node, type, propertyName).value; +} + +function convertStringArray(node) { + if ( node.type !== Syntax.ArrayExpression ) { + throw new Error("not an array"); + } + const result = []; + for ( let i = 0; i < node.elements.length; i++ ) { + if ( !isStringLiteral(node.elements[i]) ) { + throw new Error("not a string literal"); + } + result.push( convertValue(node.elements[i]) ); + } + // console.log(result); + return result; +} + +function convertDragDropValue(node, cardinality) { + const mDefaults = { draggable : false, droppable: false }; + let mDragDropValue; + + if ( node.type === Syntax.ObjectExpression ) { + mDragDropValue = (node.properties || []).reduce(function(oObject, oProperty) { + const sKey = getPropertyKey(oProperty); + if (mDefaults.hasOwnProperty(sKey)) { + oObject[sKey] = convertValue(oProperty.value); + } + return oObject; + }, {}); + } else if ( node.type === Syntax.Literal ) { + mDragDropValue = { + draggable : node.value, + droppable : node.value + }; + } else { + throw new Error("not a valid dnd node"); + } + + return Object.assign(mDefaults, mDragDropValue); +} + +function collectVisibilityInfo(settings, doclet, className, n) { + const validVisibilities = new Set(['public', 'hidden']); + const validAccesses = new Set(['public', 'protected', 'restricted', 'private']); + + let visibility = (settings.visibility && settings.visibility.value.value) || "public"; + + if (!validVisibilities.has(visibility)) { + error(`${className}: Invalid visibility '${visibility}' in runtime metadata defined for managed setting '${n}'. Valid options are ${Array.from(validVisibilities).join(', ')}.`); + } + + if (doclet?.access) { + let access = doclet.access; + + if (!validAccesses.has(access)) { + error(`${className}: Invalid JSDoc visibility '${access}' defined for managed setting '${n}'. Valid options are ${Array.from(validAccesses).join(', ')}.`); + } + + if (visibility === 'hidden' && (access === 'public' || access === 'protected' || access === 'restricted')) { + // force access to private to avoid inconsistencies in libraries that ignore JSDoc errors + error(`${className}: Inconsistent visibility settings detected. Runtime metadata sets visibility to '${visibility}', while JSDoc defines it as '${access}' for the managed setting '${n}'. Forcing visibility to 'hidden'.`); + access = "private"; + } + if (visibility === 'public' && access === 'private') { + // force access to 'restricted' to avoid inconsistencies in libraries that ignore JSDoc errors + ui5data(doclet).stakeholders ??= []; + if ( !doclet.__ui5.stakeholders.includes(className) ) { + doclet.__ui5.stakeholders.push(className); + } + error(`${className}: Inconsistent visibility settings detected. Runtime metadata sets visibility to '${visibility}', while JSDoc defines it as '${access}' for the managed setting '${n}'. Forcing visibility to 'restricted'.`); + access = "restricted"; + } + + if (visibility === "public" && (access === "restricted" || access === "protected")) { + visibility = access; + } + } + + return visibility; +} + +function convertDefaultClass(settings, doclet, n) { + const node = settings.defaultClass?.value; + if ( node == null ) { + // check if a @default tag is given (e.g. sap.ui.core.Element#customData) + // This is only taken into accout when there is no defaultClass property in the metadata + if ( doclet?.defaultvalue != null ) { + if ( typeof doclet.defaultvalue === "string" ) { + return doclet.defaultvalue; + } + error(`could not derive defaultClass for aggregation ${n} from default tag ${doclet.defaultvalue}`); + } + return undefined; + } + if ( node.type === Syntax.Identifier ) { + if ( currentModule.localNames[node.name]?.module ) { + return currentModule.localNames[node.name].module; + } + error(`could not derive defaultClass for aggregation ${n} from identifier ${node.name}`); + return "unknown"; + } + error(`could not derive defaultClass for aggregation ${n} from node of type ${node.type}`); + return "unknown"; +} + +function collectClassInfo(extendCall, classDoclet) { + + let baseType; + if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) { + baseType = classDoclet.augments[0]; + } + if ( isMemberExpression(extendCall.callee) ) { + const baseCandidate = getResolvedObjectName(extendCall.callee.object); + if ( baseCandidate && baseType == null ) { + baseType = baseCandidate; + } else if ( baseCandidate !== baseType ) { + future(`documented base type '${baseType}' doesn't match technical base type '${baseCandidate}'`); + } + } + + const oClassInfo = { + name : convertValue(extendCall.arguments[0]), + baseType : baseType, + interfaces : [], + doc : classDoclet && classDoclet.description, + deprecation : classDoclet && classDoclet.deprecated, + since : classDoclet && classDoclet.since, + experimental : classDoclet && classDoclet.experimental, + specialSettings : {}, + properties : {}, + aggregations : {}, + associations : {}, + events : {}, + methods : {}, + annotations : {}, + designtime: false, + stereotype: null, + metadataClass: undefined + }; + + function upper(n) { + return n.slice(0, 1).toUpperCase() + n.slice(1); + } + + function each(node, defaultKey, callback) { + const map = node && createPropertyMap(node.value); + if ( map ) { + for (const n in map) { + if ( Object.hasOwn(map, n) ) { + const doclet = getLeadingDoclet(map[n]); + const settings = createPropertyMap(map[n].value, defaultKey); + if ( settings == null ) { + warning(`no valid metadata for ${n} (AST type '${map[n].value.type}')`); + continue; + } + + callback(n, settings, doclet, map[n]); + } + } + } + } + + if ( extendCall.arguments.length > 2 ) { + // new class defines its own metadata class type + const metadataClass = getResolvedObjectName(extendCall.arguments[2]); + if ( metadataClass ) { + oClassInfo.metadataClass = getResolvedObjectName(extendCall.arguments[2]); + debug(`found metadata class name '${oClassInfo.metadataClass}'`); + } else { + future(`cannot understand metadata class parameter (AST node type '${extendCall.arguments[2].type}')`); + } + } + + const classInfoNode = extendCall.arguments[1]; + const classInfoMap = createPropertyMap(classInfoNode); + if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) { + warning(`class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '${classInfoMap.metadata.value.type}'.`); + return null; + } + + const metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value); + if ( metadata ) { + + debug(` analyzing metadata for '${oClassInfo.name}'`); + + // Read the stereotype information from the metadata + oClassInfo.stereotype = (metadata.stereotype && metadata.stereotype.value.value) || undefined; + + oClassInfo.library = (metadata.library && metadata.library.value.value) || undefined; + + oClassInfo["abstract"] = !!(metadata["abstract"] && metadata["abstract"].value.value); + oClassInfo["final"] = !!(metadata["final"] && metadata["final"].value.value); + oClassInfo.dnd = metadata.dnd && convertDragDropValue(metadata.dnd.value); + + if ( metadata.interfaces ) { + oClassInfo.interfaces = convertStringArray(metadata.interfaces.value); + } + + each(metadata.specialSettings, "type", (n, settings, doclet) => { + oClassInfo.specialSettings[n] = { + name : n, + doc : doclet && doclet.description, + since : doclet && doclet.since, + deprecation : doclet && doclet.deprecated, + experimental : doclet && doclet.experimental, + visibility: collectVisibilityInfo(settings, doclet, oClassInfo.name, n), + stakeholders: doclet && doclet.__ui5 && doclet.__ui5.stakeholders, + type : settings.type ? settings.type.value.value : "any" + }; + }); + + oClassInfo.defaultProperty = (metadata.defaultProperty && metadata.defaultProperty.value.value) || undefined; + + each(metadata.properties, "type", (n, settings, doclet) => { + const N = upper(n); + let methods; + const dataType = settings.type ? settings.type.value.value : "string"; + oClassInfo.properties[n] = { + name: n, + doc: doclet && doclet.description, + since: doclet && doclet.since, + deprecation: doclet && doclet.deprecated, + experimental: doclet && doclet.experimental, + visibility: collectVisibilityInfo(settings, doclet, oClassInfo.name, n), + stakeholders: doclet && doclet.__ui5 && doclet.__ui5.stakeholders, + type: dataType, + defaultValue: settings.defaultValue ? convertValueWithRaw(settings.defaultValue.value, dataType, n) : null, + group: settings.group ? settings.group.value.value : 'Misc', + bindable: settings.bindable ? !!convertValue(settings.bindable.value) : false, + methods: (methods = { + "get": "get" + N, + "set": "set" + N + }) + }; + if (oClassInfo.properties[n].bindable) { + methods["bind"] = "bind" + N; + methods["unbind"] = "unbind" + N; + } + // Check for @type definition + if (doclet?.type?.names) { + oClassInfo.properties[n].type = doclet?.type?.names.join('|'); + + if (oClassInfo.properties[n].type !== dataType) { + oClassInfo.properties[n].dataType = dataType; + } + } + }); + + oClassInfo.defaultAggregation = (metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined; + + each(metadata.aggregations, "type", (n, settings, doclet) => { + const N = upper(n); + let methods; + + const aggr = oClassInfo.aggregations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility: collectVisibilityInfo(settings, doclet, oClassInfo.name, n), + stakeholders: doclet && doclet.__ui5 && doclet.__ui5.stakeholders, + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", + altTypes: settings.altTypes ? convertStringArray(settings.altTypes.value) : undefined, + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n", + bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false, + defaultClass: convertDefaultClass(settings, doclet, n), + methods: (methods = { + "get": "get" + N, + "destroy": "destroy" + N + }) + }; + + aggr.dnd = settings.dnd && convertDragDropValue(settings.dnd.value, aggr.cardinality); + + if ( aggr.cardinality === "0..1" ) { + methods["set"] = "set" + N; + } else { + const N1 = upper(aggr.singularName); + methods["insert"] = "insert" + N1; + methods["add"] = "add" + N1; + methods["remove"] = "remove" + N1; + methods["indexOf"] = "indexOf" + N1; + methods["removeAll"] = "removeAll" + N; + } + if ( aggr.bindable ) { + methods["bind"] = "bind" + N; + methods["unbind"] = "unbind" + N; + } + }); + + each(metadata.associations, "type", (n, settings, doclet) => { + const N = upper(n); + let methods; + + oClassInfo.associations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility: collectVisibilityInfo(settings, doclet, oClassInfo.name, n), + stakeholders: doclet && doclet.__ui5 && doclet.__ui5.stakeholders, + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1", + methods: (methods = { + "get": "get" + N + }) + }; + if ( oClassInfo.associations[n].cardinality === "0..1" ) { + methods["set"] = "set" + N; + } else { + const N1 = upper(oClassInfo.associations[n].singularName); + methods["add"] = "add" + N1; + methods["remove"] = "remove" + N1; + methods["removeAll"] = "removeAll" + N; + } + }); + + each(metadata.events, null, (n, settings, doclet) => { + const N = upper(n); + + const info = oClassInfo.events[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility: collectVisibilityInfo(settings, doclet, oClassInfo.name, n), + stakeholders: doclet && doclet.__ui5 && doclet.__ui5.stakeholders, + allowPreventDefault : !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value), + enableEventBubbling : !!(settings.enableEventBubbling && settings.enableEventBubbling.value.value), + parameters : {}, + methods: { + "attach": "attach" + N, + "detach": "detach" + N, + "fire": "fire" + N + } + }; + each(settings.parameters, "type", (pName, pSettings, pDoclet) => { + info.parameters[pName] = { + name : pName, + doc : pDoclet && pDoclet.description, + deprecation : pDoclet && pDoclet.deprecated, + since : pDoclet && pDoclet.since, + experimental : pDoclet && pDoclet.experimental, + type : pSettings && pSettings.type ? pSettings.type.value.value : "" + }; + }); + }); + + const designtime = (metadata.designtime && convertValue(metadata.designtime.value)) || (metadata.designTime && convertValue(metadata.designTime.value)); + if ( typeof designtime === 'string' || typeof designtime === 'boolean' ) { + oClassInfo.designtime = designtime; + } + // console.log(oClassInfo.name + ":" + JSON.stringify(oClassInfo, null, " ")); + } + + if (currentModule.defaultExport + && currentModule.localNames[currentModule.defaultExport] + && currentModule.localNames[currentModule.defaultExport].class === oClassInfo.name) { + // debug("class " + oClassInfo.name + " identified as default export of module " + currentModule.module); + oClassInfo.export = ""; + } else if (currentModule.defaultExportClass + && currentModule.defaultExportClass === oClassInfo.name) { + // debug("class " + oClassInfo.name + " identified as default export of module " + currentModule.module + " (immediate return)"); + oClassInfo.export = ""; + } + + // remember class info by name + classInfos[oClassInfo.name] = oClassInfo; + + return oClassInfo; +} + +function collectDesigntimeInfo(dtNodeArgument) { + + function each(node, defaultKey, callback) { + const map = node && createPropertyMap(node.value); + if ( map ) { + for (const n in map) { + if ( Object.hasOwn(map, n) ) { + const doclet = getLeadingDoclet(map[n], true); + const settings = createPropertyMap(map[n].value, defaultKey); + if ( settings == null ) { + warning(`no valid metadata for ${n} (AST type '${map[n].value.type}')`); + continue; + } + + callback(n, settings, doclet, map[n]); + } + } + } + } + + let oDesigntimeInfo; + + const map = createPropertyMap(dtNodeArgument); + + if (map.annotations) { + + oDesigntimeInfo = { + annotations: {} + }; + + each(map.annotations, null, (n, settings, doclet) => { + const appliesTo = [], + targets = []; + + if (settings.appliesTo) { + for (let i = 0; i < settings.appliesTo.value.elements.length; i++) { + appliesTo.push(settings.appliesTo.value.elements[i].value); + } + } + + if (settings.target) { + for (let i = 0; i < settings.target.value.elements.length; i++) { + targets.push(settings.target.value.elements[i].value); + } + } + + oDesigntimeInfo.annotations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since || settings.since && settings.since.value.value, + namespace: settings.namespace && settings.namespace.value.value, + annotation: settings.annotation && settings.annotation.value.value, + appliesTo: appliesTo, + target: targets, + interpretation: settings.interpretation && settings.interpretation.value.value, + defaultValue: settings.defaultValue && settings.defaultValue.value.value + }; + + const oAnno = oDesigntimeInfo.annotations[n].annotation; + const iPos = oAnno && oAnno.lastIndexOf("."); + + if ( !oDesigntimeInfo.annotations[n].namespace && iPos > 0 ) { + oDesigntimeInfo.annotations[n].namespace = oAnno.slice(0, iPos); + oDesigntimeInfo.annotations[n].annotation = oAnno.slice(iPos + 1); + } + }); + } + + return oDesigntimeInfo; +} + +function determineValueRangeBorder(range, expression, varname, inverse) { + if ( expression.type === Syntax.BinaryExpression ) { + let value; + if ( expression.left.type === Syntax.Identifier && expression.left.name === varname && expression.right.type === Syntax.Literal ) { + value = expression.right.value; + } else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier && expression.right.name === varname ) { + inverse = !inverse; + value = expression.left.value; + } else { + return false; + } + switch (expression.operator) { + case '<': + range[inverse ? 'minExclusive' : 'maxExclusive'] = value; + break; + case '<=': + range[inverse ? 'minInclusive' : 'maxInclusive'] = value; + break; + case '>=': + range[inverse ? 'maxInclusive' : 'minInclusive'] = value; + break; + case '>': + range[inverse ? 'maxExclusive' : 'minExclusive'] = value; + break; + default: + return false; + } + return true; + } + return false; +} + +function determineValueRange(expression, varname, inverse) { + const range = {}; + if ( expression.type === Syntax.LogicalExpression + && expression.left.type === Syntax.BinaryExpression + && expression.right.type === Syntax.BinaryExpression ) { + + if ( expression.operator === "&&" + && determineValueRangeBorder(range, expression.left, varname, inverse) + && determineValueRangeBorder(range, expression.right, varname, inverse) ) { + return range; + } else if ( ["||", "??"].includes(expression.operator) + && ( determineValueRangeBorder(range, expression.left, varname, inverse) + || determineValueRangeBorder(range, expression.right, varname, inverse) )) { + return range; + } + + } else if ( expression.type === Syntax.BinaryExpression + && determineValueRangeBorder(range, expression, varname, inverse) ) { + return range; + } + return undefined; +} + +function collectDataTypeInfo(extendCall, classDoclet) { + const args = extendCall.arguments; + let i = 0, + name, def, base, pattern, range; + + if ( i < args.length && isStringLiteral(args[i]) ) { + name = convertValue(args[i++]); + } + if ( i < args.length && args[i].type === Syntax.ObjectExpression ) { + def = createPropertyMap(args[i++]); + } + if ( i < args.length ) { + const node = resolvePotentialWrapperExpression(args[i]); + + if ( isStringLiteral(args[i]) ) { + base = convertValue(args[i++]); + } else if ( isCaleeMemberExpression(node) + && getResolvedObjectName(node.callee.object) === "sap.ui.base.DataType" + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'getType' + && node.arguments.length === 1 + && isStringLiteral(node.arguments[0]) ) { + base = convertValue(args[i++].arguments[0]); + } else { + future(`could not identify base type of data type '${name}'`); + } + } else { + base = "any"; + } + + const isArrowExpression = isArrowFuncExpression(def && def.isValid && def.isValid.value); + + if ( def + && def.isValid + && [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(def.isValid.value.type) + && def.isValid.value.params.length === 1 + && def.isValid.value.params[0].type === Syntax.Identifier + && (isArrowExpression || def.isValid.value.body.body.length === 1) ) { + + const varname = def.isValid.value.params[0].name; + const stmt = isArrowExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; + + if ( isReturningNode(stmt) || isArrowExpression ) { + const stmtArgument = resolvePotentialWrapperExpression( + isArrowExpression ? stmt : stmt.argument + ); + if ( isCaleeMemberExpression(stmtArgument) + && stmtArgument.callee.object.type === Syntax.Literal + && stmtArgument.callee.object.regex + && stmtArgument.callee.property.type === Syntax.Identifier + && stmtArgument.callee.property.name === 'test' ) { + pattern = stmtArgument.callee.object.regex.pattern; + // console.log(pattern); + } else { + range = determineValueRange(stmtArgument, varname, false); + } + } else if ( stmt.type === Syntax.IfStatement + && stmt.consequent.type === Syntax.BlockStatement + && stmt.consequent.body.length === 1 + && stmt.consequent.body[0].type === Syntax.ReturnStatement + && stmt.consequent.body[0].argument + && stmt.consequent.body[0].argument.type === Syntax.Literal + && typeof stmt.consequent.body[0].argument.value === 'boolean' + && stmt.alternate.type === Syntax.BlockStatement + && stmt.alternate.body.length === 1 + && stmt.alternate.body[0].type === Syntax.ReturnStatement + && stmt.alternate.body[0].argument + && stmt.alternate.body[0].argument.type === Syntax.Literal + && typeof stmt.alternate.body[0].argument.value === 'boolean' + && stmt.consequent.body[0].argument.value !== stmt.alternate.body[0].argument.value ) { + const inverse = stmt.alternate.body[0].argument.value; + range = determineValueRange(stmt.test, varname, inverse); + } else { + debug("unexpected implementation of a DataType's isValid() implementation: ", stmt); + } + } + + // remember type info by name + if ( name && def && base ) { + typeInfos[name] = { + name: name, + def: def, + pattern: pattern, + range: range, + base: base + }; + // console.log("found data type:", typeInfos[name]); + } +} + +const rEmptyLine = /^\s*$/; + +function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename, commentAlreadyProcessed) { + + const newStyle = !!pluginConfig.newStyle, + includeSettings = !!pluginConfig.includeSettingsInConstructor, + rawClassComment = getRawComment(classComment); + let lines, link; + + function isEmpty(obj) { + if ( !obj ) { + return true; + } + for (const n in obj) { + if ( Object.hasOwn(obj, n) ) { + return false; + } + } + return true; + } + + function jsdocCommentFound(comment) { + parser.emit('jsdocCommentFound', { + event:'jsdocCommentFound', + comment : comment, + lineno : node.loc.start.line, + filename : filename, + range : [ node.range[0], node.range[0] ] + }, parser); + } + + function removeDuplicateEmptyLines(lines) { + const l = lines.length; + let lastWasEmpty = false, j, i; + + for (i = 0, j = 0; i < l; i++) { + const line = lines[i]; + if ( line == null || rEmptyLine.test(line) ) { + if ( !lastWasEmpty ) { + lines[j++] = line; + } + lastWasEmpty = true; + } else { + lines[j++] = line; + lastWasEmpty = false; + } + } + return j < i ? lines.slice(0, j) : lines; + } + + function newJSDoc(lines) { + //console.log("add completely new jsdoc comment to prog " + node.type + ":" + node.nodeId + ":" + Object.keys(node)); + + lines = Array.prototype.concat.apply([], lines); // flatten + lines = removeDuplicateEmptyLines(lines); + lines.push("@synthetic"); + + const comment = " * " + lines.join("\r\n * "); + jsdocCommentFound("/**\r\n" + comment + "\r\n */"); + + const m = /@name\s+([^\r\n\t ]+)/.exec(comment); + debug(` creating synthetic comment '${m && m[1]}'`); + } + + function rname(prefix, n, _static) { + return (_static ? "." : "#") + prefix + n.slice(0, 1).toUpperCase() + n.slice(1); + } + + function name(prefix, n, _static) { + return oClassInfo.name + rname(prefix, n, _static); + } + + /* + * creates a JSDoc type string from the given metadata info object. + * It takes into account the type, the altTypes and the cardinality + * (the latter only if componentTypeOnly is not set). + */ + function makeTypeString(aggr, componentTypeOnly) { + let s = aggr.type; + if ( aggr.altTypes ) { + s = s + "|" + aggr.altTypes.join("|"); + } + if ( !componentTypeOnly && aggr.cardinality === "0..n" ) { + // if multiple types are allowed, use Array<...> for proper grouping + if ( aggr.altTypes ) { + s = "Array<" + s + ">"; + } else { + s = s + "[]"; + } + } + return s; + } + +// function shortname(s) { +// return s.slice(s.lastIndexOf('.') + 1); +// } + + /** + * Creates all necessary tags to reflect the develpment state + * (ui5-experimental-since/experimental, since and/or deprecated). + * + * If the experimental property of the info object contains only a SemVer, + * it is assumed to stem from an @ui5-experimental-since tag, otherwise, if + * it's not empty, it is assumed to stem from an experimental tag. + * @returns {string[]} List of tags + */ + function createDevelopmentStateTags(info) { + const tags = []; + if (isSemVer(info.experimental)) { + tags.push(`@ui5-experimental-since ${info.experimental}`); + } else if (info.experimental) { + tags.push(`@experimental ${info.experimental}`); + } + if (info.since) { + tags.push(`@since ${info.since}`); + } + if (info.deprecation) { + tags.push(`@deprecated ${info.deprecation}`); + } + return tags; + } + + function createVisibilityTags(access, stakeholders) { + if ( access === "restricted" ) { + return [ + "@private", + "@ui5-restricted" + (Array.isArray(stakeholders) ? " " + stakeholders.join(", ") : "") + ]; + } + return "@" + access; + } + + function createVisibilityTagsForSetting(settingInfo, classAccess) { + let access = settingInfo.visibility ?? "public"; + if ( classAccess === "restricted" && (access === "public" || access === "protected")) { + access = "restricted"; + } + return createVisibilityTags(access, settingInfo.stakeholders); + } + + const HUNGARIAN_PREFIXES = { + 'int' : 'i', + 'boolean' : 'b', + 'float' : 'f', + 'string' : 's', + 'function' : 'fn', + 'object' : 'o', + 'regexp' : 'r', + 'jQuery' : '$', + 'any' : 'o', + 'variant' : 'v', + 'map' : 'm' + }; + + function varname(n, type, property) { + const prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o"); + return prefix + n.slice(0, 1).toUpperCase() + n.slice(1); + } + + function generateParamTag(n, type, description, defaultValue){ + let s = "@param {" + type + "} "; + + if (defaultValue !== null){ + s += "[" + varname(n, type, true) + "=" + defaultValue.raw + "]"; + } else { + s += varname(n, type, true); + } + + s += " " + description; + + return s; + } + + // add a list of the possible settings if and only if + // - documentation for the constructor exists + // - no (generated) documentation for settings exists already + // - a suitable place for inserting the settings can be found + const m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment); + const p = m ? m.index : -1; + const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0; + const classAccess = doclet?.access ?? "public"; + const visibility = createVisibilityTags(classAccess, doclet?.__ui5?.stakeholders); + const thisClass = 'this'; // oClassInfo.name + + // heuristic to recognize a ManagedObject + const isManagedObject = ( + /@extends\s+sap\.ui\.(?:base\.ManagedObject|core\.(?:Element|Control|Component))(?:\s|$)/.test(rawClassComment) + || oClassInfo.library + || !isEmpty(oClassInfo.specialSettings) + || !isEmpty(oClassInfo.properties) + || !isEmpty(oClassInfo.aggregations) + || !isEmpty(oClassInfo.associations) + || !isEmpty(oClassInfo.events) + ); + + if ( p >= 0 && !hasSettingsDocs ) { + lines = [ + "" + ]; + + if ( isManagedObject ) { // only a ManagedObject has settings + + if ( oClassInfo.name !== "sap.ui.base.ManagedObject" ) { + // add the hint for the general description only when the current class is not ManagedObject itself + lines.push( + "", + "Accepts an object literal mSettings that defines initial", + "property values, aggregated and associated objects as well as event handlers.", + "See {@link sap.ui.base.ManagedObject#constructor} for a general description of the syntax of the settings object." + ); + } + + // add the settings section only if there are any settings + if ( !isEmpty(oClassInfo.properties) + || !isEmpty(oClassInfo.aggregations) + || !isEmpty(oClassInfo.associations) + || !isEmpty(oClassInfo.events) ) { + + lines.push( + "", + includeSettings ? "" : "@ui5-settings", + "The supported settings are:", + "
      " + ); + if ( !isEmpty(oClassInfo.properties) ) { + lines.push("
    • Properties"); + lines.push("
        "); + for (const n in oClassInfo.properties) { + lines.push("
      • {@link " + rname("get", n) + " " + n + "} : " + oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null && oClassInfo.properties[n].defaultValue.value !== null ? " (default: " + oClassInfo.properties[n].defaultValue.raw + ")" : "") + (oClassInfo.defaultProperty === n ? " (default)" : "") + "
      • "); + } + lines.push("
      "); + lines.push("
    • "); + } + if ( !isEmpty(oClassInfo.aggregations) ) { + lines.push("
    • Aggregations"); + lines.push("
        "); + for (const n in oClassInfo.aggregations) { + if ( oClassInfo.aggregations[n].visibility !== "hidden" ) { + lines.push("
      • {@link " + rname("get", n) + " " + n + "} : " + makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation === n ? " (default)" : "") + "
      • "); + } + } + lines.push("
      "); + lines.push("
    • "); + } + if ( !isEmpty(oClassInfo.associations) ) { + lines.push("
    • Associations"); + lines.push("
        "); + for (const n in oClassInfo.associations) { + lines.push("
      • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
      • "); + } + lines.push("
      "); + lines.push("
    • "); + } + if ( !isEmpty(oClassInfo.events) ) { + lines.push("
    • Events"); + lines.push("
        "); + for (const n in oClassInfo.events) { + lines.push("
      • {@link " + "#event:" + n + " " + n + "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
      • "); + } + lines.push("
      "); + lines.push("
    • "); + } + lines.push("
    "); + + // add the reference to the base class only if this is not ManagedObject and if the base class is known + if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType ) { + lines.push( + "", + "In addition, all settings applicable to the base type {@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "}", + "can be used as well." + ); + } + lines.push(""); + + } else if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType && oClassInfo.hasOwnProperty("abstract") ) { + + // if a class has no settings, but metadata, point at least to the base class - if it makes sense + lines.push( + "", + newStyle && !includeSettings ? "@ui5-settings" : "", + "This class does not have its own settings, but all settings applicable to the base type", + "{@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "} can be used." + ); + + } + } + + debug(" enhancing constructor documentation with settings"); + let enhancedComment = + rawClassComment.slice(0, p) + + "\n * " + removeDuplicateEmptyLines(lines).join("\n * ") + + (commentAlreadyProcessed ? "@ui5-updated-doclet\n * " : "") + + rawClassComment.slice(p); + enhancedComment = preprocessComment({ comment : enhancedComment, lineno : classComment.lineno }); + + if ( commentAlreadyProcessed ) { + jsdocCommentFound(enhancedComment); + } else { + setRawComment(classComment, enhancedComment); + } + + } + + newJSDoc([ + "Returns a metadata object for class " + oClassInfo.name + ".", + "", + "@returns {sap.ui.base.Metadata} Metadata object describing this class", + visibility, + "@static", + "@name " + name("getMetadata", "", true), + "@function" + ]); + + if ( !oClassInfo["final"] ) { + newJSDoc([ + "Creates a new subclass of class " + oClassInfo.name + " with name sClassName", + "and enriches it with the information contained in oClassInfo.", + "", + "oClassInfo might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", + "", + "@param {string} sClassName Name of the class being created", + "@param {object} [oClassInfo] Object literal with information about the class", + "@param {function} [FNMetaImpl] Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class", + "@returns {function} Created class / constructor function", + visibility, + "@static", + "@name " + name("extend", "", true), + "@function" + ]); + } + + for (const n in oClassInfo.properties ) { + const info = oClassInfo.properties[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + const devStateTags = createDevelopmentStateTags(info); + const visibilityTags = createVisibilityTagsForSetting(info, classAccess); + + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + "Gets current value of property " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + info.defaultValue !== null && info.defaultValue.value !== null + ? "Default value is " + (info.defaultValue.value === "" ? "empty string" : info.defaultValue.raw) + "." + : "", + "@returns {" + info.type + "} Value of property " + n + "", + devStateTags, + visibilityTags, + "@name " + name("get", n), + "@function" + ]); + newJSDoc([ + "Sets a new value for property " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + "When called with a value of null or undefined, the default value of the property will be restored.", + "", + info.defaultValue !== null && info.defaultValue.value !== null + ? "Default value is " + (info.defaultValue.value === "" ? "empty string" : info.defaultValue.raw) + "." + : "", + generateParamTag(n, info.type, "New value for property " + n + "", info.defaultValue), + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("set", n), + "@function" + ]); + if ( info.bindable ) { + newJSDoc([ + "Binds property " + link + " to model data.", + "", + "See {@link sap.ui.base.ManagedObject#bindProperty ManagedObject.bindProperty} for a ", + "detailed description of the possible properties of oBindingInfo", + "@param {sap.ui.base.ManagedObject.PropertyBindingInfo} oBindingInfo The binding information", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("bind", n), + "@function" + ]); + newJSDoc([ + "Unbinds property " + link + " from model data.", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("unbind", n), + "@function" + ]); + } + } + + for (const n in oClassInfo.aggregations ) { + const info = oClassInfo.aggregations[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + const devStateTags = createDevelopmentStateTags(info); + const visibilityTags = createVisibilityTagsForSetting(info, classAccess); + + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + "Gets content of aggregation " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + n === info.defaultAggregation ? "Note: this is the default aggregation for " + n + "." : "", + "@returns {" + makeTypeString(info) + "}", + devStateTags, + visibilityTags, + "@name " + name("get", n), + "@function" + ]); + if ( info.cardinality === "0..n" ) { + const n1 = info.singularName; + newJSDoc([ + "Inserts a " + n1 + " into the aggregation " + link + ".", + "", + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", + "@param {int}", + " iIndex The 0-based index the " + n1 + " should be inserted at; for", + " a negative value of iIndex, the " + n1 + " is inserted at position 0; for a value", + " greater than the current size of the aggregation, the " + n1 + " is inserted at", + " the last position", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("insert", n1), + "@function" + ]); + newJSDoc([ + "Adds some " + n1 + " to the aggregation " + link + ".", + + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("add", n1), + "@function" + ]); + newJSDoc([ + "Removes a " + n1 + " from the aggregation " + link + ".", + "", + "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1, "variant") + " The " + n1 + " to remove or its index or id", + "@returns {" + makeTypeString(info, true) + "|null} The removed " + n1 + " or null", + devStateTags, + visibilityTags, + "@name " + name("remove", n1), + "@function" + ]); + newJSDoc([ + "Removes all the controls from the aggregation " + link + ".", + "", + "Additionally, it unregisters them from the hosting UIArea.", + "@returns {" + makeTypeString(info) + "} An array of the removed elements (might be empty)", + devStateTags, + visibilityTags, + "@name " + name("removeAll", n), + "@function" + ]); + newJSDoc([ + "Checks for the provided " + info.type + " in the aggregation " + link + ".", + "and returns its index if found or -1 otherwise.", + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " whose index is looked for", + "@returns {int} The index of the provided control in the aggregation if found, or -1 otherwise", + devStateTags, + visibilityTags, + "@name " + name("indexOf", n1), + "@function" + ]); + } else { + newJSDoc([ + "Sets the aggregated " + link + ".", + "@param {" + makeTypeString(info) + "} " + varname(n, info.altTypes ? "variant" : info.type) + " The " + n + " to set", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("set", n), + "@function" + ]); + } + newJSDoc([ + "Destroys " + (info.cardinality === "0..n" ? "all " : "") + "the " + n + " in the aggregation " + link + ".", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("destroy", n), + "@function" + ]); + if ( info.bindable ) { + newJSDoc([ + "Binds aggregation " + link + " to model data.", + "", + "See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a ", + "detailed description of the possible properties of oBindingInfo.", + "@param {sap.ui.base.ManagedObject.AggregationBindingInfo} oBindingInfo The binding information", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("bind", n), + "@function" + ]); + newJSDoc([ + "Unbinds aggregation " + link + " from model data.", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("unbind", n), + "@function" + ]); + } + } + + for (const n in oClassInfo.associations ) { + const info = oClassInfo.associations[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + const devStateTags = createDevelopmentStateTags(info); + const visibilityTags = createVisibilityTagsForSetting(info, classAccess); + + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + info.cardinality === "0..n" ? + "Returns array of IDs of the elements which are the current targets of the association " + link + "." : + "ID of the element which is the current target of the association " + link + ", or null.", + "", + newStyle && info.doc ? info.doc : "", + "", + `@returns {${info.cardinality === "0..n" ? "sap.ui.core.ID[]" : "sap.ui.core.ID|null"}}`, + devStateTags, + visibilityTags, + "@name " + name("get", n), + "@function" + ]); + if ( info.cardinality === "0..n" ) { + const n1 = info.singularName; + newJSDoc([ + "Adds some " + n1 + " into the association " + link + ".", + "", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + " to add; if empty, nothing is inserted", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("add", n1), + "@function" + ]); + newJSDoc([ + "Removes an " + n1 + " from the association named " + link + ".", + "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n1 + " to be removed or its index or ID", + "@returns {sap.ui.core.ID|null} The removed " + n1 + " or null", + devStateTags, + visibilityTags, + "@name " + name("remove", n1), + "@function" + ]); + newJSDoc([ + "Removes all the controls in the association named " + link + ".", + "@returns {sap.ui.core.ID[]} An array of the removed elements (might be empty)", + devStateTags, + visibilityTags, + "@name " + name("removeAll", n), + "@function" + ]); + } else { + newJSDoc([ + "Sets the associated " + link + ".", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + " ID of an element which becomes the new target of this " + n + " association; alternatively, an element instance may be given", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("set", n), + "@function" + ]); + } + } + + for (const n in oClassInfo.events ) { + const info = oClassInfo.events[n]; + const devStateTags = createDevelopmentStateTags(info); + const visibilityTags = createVisibilityTagsForSetting(info, classAccess); + + //link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; + link = "{@link #event:" + n + " " + n + "}"; + + lines = [ + info.doc ? info.doc : "", + "", + info.allowPreventDefault ? "Listeners may prevent the default action of this event by calling the preventDefault method on the event object." : "", + "", + info.enableEventBubbling ? "This event bubbles up the control hierarchy." : "", + "", + "@name " + oClassInfo.name + "#" + n, + "@event", + devStateTags, + "@param {sap.ui.base.Event} oControlEvent", + "@param {sap.ui.base.EventProvider} oControlEvent.getSource", + "@param {object} oControlEvent.getParameters" + ]; + for (const pName in info.parameters ) { + lines.push( + "@param {" + (info.parameters[pName].type || "") + "} oControlEvent.getParameters." + pName + " " + (info.parameters[pName].doc || "") + ); + } + lines.push(visibilityTags); + + newJSDoc(lines); + + newJSDoc([ + "Attaches event handler fnFunction to the " + link + " event of this " + oClassInfo.name + ".", + "", + "When called, the context of the event handler (its this) will be bound to oListener if specified, ", + "otherwise it will be bound to this " + oClassInfo.name + " itself.", + "", + !newStyle && info.doc ? info.doc : "", + "", + "@param {object}", + " [oData] An application-specific payload object that will be passed to the event handler along with the event object when firing the event", + "@param {function(sap.ui.base.Event):void}", + " fnFunction The function to be called when the event occurs", + "@param {object}", + " [oListener] Context object to call the event handler with. Defaults to this " + oClassInfo.name + " itself", + "", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("attach", n), + "@function" + ]); + newJSDoc([ + "Detaches event handler fnFunction from the " + link + " event of this " + oClassInfo.name + ".", + "", + "The passed function and listener object must match the ones used for event registration.", + "", + "@param {function(sap.ui.base.Event):void}", + " fnFunction The function to be called, when the event occurs", + "@param {object}", + " [oListener] Context object on which the given function had to be called", + "@returns {" + thisClass + "} Reference to this in order to allow method chaining", + devStateTags, + visibilityTags, + "@name " + name("detach", n), + "@function" + ]); + + // build documentation for fireEvent. It contains conditional parts which makes it a bit more complicated + lines = [ + "Fires event " + link + " to attached listeners." + ]; + if ( info.allowPreventDefault ) { + lines.push( + "", + "Listeners may prevent the default action of this event by calling the preventDefault method on the event object.", + "The return value of this method indicates whether the default action should be executed.", + ""); + } + lines.push( + "", + "@param {object} [mParameters] Parameters to pass along with the event" + ); + if ( !isEmpty(info.parameters) ) { + for (const pName in info.parameters) { + lines.push( + "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + (info.parameters[pName].doc || "") + ); + } + lines.push(""); + } + if ( info.allowPreventDefault ) { + lines.push("@returns {boolean} Whether or not to prevent the default action"); + } else { + lines.push("@returns {" + thisClass + "} Reference to this in order to allow method chaining"); + } + + let vis = "protected"; // default, when event and class are public + if (info.visibility !== "public") { + vis = info.visibility; // reduced JSDoc visibility of event + } + if (classAccess === "restricted") { + vis = "restricted"; // reduced visibility of class + } + + lines.push( + devStateTags, + createVisibilityTags(vis, info.stakeholders), + "@name " + name("fire", n), + "@function" + ); + newJSDoc(lines); + } + +} + +function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) { +} + +/** + * Creates a human readable location info for a given doclet. + * @param {Doclet} doclet Doclet to get a location info for + * @returns {string} A human readable location info + */ +function location(doclet) { + const filename = (doclet.meta && doclet.meta.filename) || "unknown"; + return " #" + ui5data(doclet).id + "@" + filename + (doclet.meta.lineno != null ? ":" + doclet.meta.lineno : "") + (doclet.synthetic ? "(synthetic)" : ""); +} + +/** + * Converts a JSDoc name to a UI5 runtime metadata name. + * + * Basically, global names in dot notation don't require a conversion. Only when the JSDoc name + * is a `module:*` name, the prefix is removed and slashes are converted to dots. + * + * Note: the conversion in the opposite direction is not well-defined as not every dot has to be + * converted to a slash (e.g. not for named exports or subtypes in another module). This + * also implies that two different JSDoc names might map to the same UI5 runtime metadata + * name. API design should avoid such ambiguities. + */ +function toRuntimeMetadataName(longname) { + return longname?.startsWith("module:") ? longname.slice("module:".length).replace(/\//g, ".") : longname; +} + +// ---- Comment handling --------------------------------------------------------------------------- + +// --- comment related functions that depend on the JSdoc version (e.g. on the used parser) + +let isDocComment; +let getLeadingCommentNode; + +// JSDoc added the node type Syntax.File with the same change that activated Babylon +// See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869 +if ( Syntax.File === 'File' ) { + + // JSDoc starting with version 3.5.0 + + isDocComment = function isDocCommentBabylon(comment) { + return comment && comment.type === 'CommentBlock' && comment.value && comment.value.charAt(0) === '*'; + }; + + getLeadingCommentNode = function getLeadingCommentNodeBabylon(node, longname) { + let leadingComments = node.leadingComments; + if ( Array.isArray(leadingComments) ) { + // in babylon, all comments are already attached to the node + // and the last one is the closest one and should win + // non-block comments have to be filtered out + leadingComments = leadingComments.filter(isDocComment); + if ( leadingComments.length > 0 ) { + return leadingComments[leadingComments.length - 1]; + } + } + return undefined; + }; + +} else { + + // JSDoc versions before 3.5.0 + + isDocComment = function isDoccommentEsprima(comment) { + return comment && comment.type === 'Block'; + }; + + getLeadingCommentNode = function getLeadingCommentNodeEsprima(node, longname) { + let comment, + leadingComments = node.leadingComments; + + // when espree is used, JSDOc attached the leading comment and the first one was picked + if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) { + comment = leadingComments[0]; + } + + // also check all comments attached to the Program node (if found) whether they refer to the same longname + // TODO check why any matches here override the direct leading comment from above + if ( longname && currentProgram && currentProgram.leadingComments && currentProgram.leadingComments.length ) { + leadingComments = currentProgram.leadingComments; + const rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, '\\.')); + for ( let i = 0; i < leadingComments.length; i++ ) { + const raw = getRawComment(leadingComments[i]); + if ( /^\/\*\*[\s\S]*\*\/$/.test(raw) && rLongname.test(raw) ) { + comment = leadingComments[i]; + // console.log("\n\n**** alternative comment found for " + longname + " on program level\n\n", comment); + break; + } + } + } + + return comment; + }; +} + +//--- comment related functions that are independent from the JSdoc version + +function getLeadingComment(node) { + const comment = getLeadingCommentNode(node); + return comment ? getRawComment(comment) : null; +} + +function getLeadingDoclet(node, preprocess) { + let comment = getLeadingComment(node); + if ( comment && preprocess ) { + comment = preprocessComment({comment:comment, lineno: node.loc.start.line }); + } + return comment ? new Doclet(comment, {}) : null; +} + +/** + * Determines the raw comment string (source code form, including leading and trailing comment markers / *...* /) from a comment node. + * Works for Esprima and Babylon based JSDoc versions. + * @param {ASTNode} commentNode Node that contains the comment. + * @returns {string} Comment string as written in the source + */ +function getRawComment(commentNode) { + // in esprima, there's a 'raw' property, in babylon, the 'raw' string has to be reconstructed from the 'value' by adding the markers + return commentNode ? commentNode.raw || '/*' + commentNode.value + '*/' : ''; +} + +function setRawComment(commentNode, newRawComment) { + if ( commentNode.raw ) { + commentNode.raw = newRawComment; + } + commentNode.value = newRawComment.slice(2, -2); +} + +/** + * Removes the mandatory comment markers and the optional but common asterisks at the beginning of each JSDoc comment line. + * + * The result is easier to parse/analyze. + * + * Implementation is a 1:1 copy from JSDoc's lib/jsdoc/doclet.js (closure function, not directly reusable) + * + * @param {string} docletSrc the source comment with or without block comment markers + * @returns {string} the unwrapped content of the JSDoc comment + * + */ +function unwrap(docletSrc) { + if (!docletSrc) { return ''; } + + // note: keep trailing whitespace for @examples + // extra opening/closing stars are ignored + // left margin is considered a star and a space + // use the /m flag on regex to avoid having to guess what this platform's newline is + docletSrc = + docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars + .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker + .replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker + .replace(/\s*\\Z$/g, ''); // remove end-marker + + return docletSrc; +} + +/** + * Inverse operation of unwrap. + * + * The prefix for lines is fixed to be " * ", lines are separated with '\n', independent from the platform. + * @param {string|string[]} lines Multiline string or an array of lines + * @returns {string} Full comment string created from the line(s) + */ +function wrap(lines) { + if ( typeof lines === "string" ) { + lines = lines.split(/\r\n?|\n/); + } + return "/**\n * " + lines.join('\n * ') + "\n */"; +} + +/** + * Pre-processes a JSDoc comment string to ensure some UI5 standards. + * + * @param {event} e Event for the new comment + * @returns {event} Returns the modified event + */ +function preprocessComment(e) { + + let src = e.comment; + + // add a default visibility + if ( !/@private|@public|@protected|@sap-restricted|@ui5-restricted/.test(src) ) { + src = unwrap(src); + src = src + "\n@private"; + src = wrap(src); + // console.log("added default visibility to '" + src + "'"); + } + + if ( /@class/.test(src) && /@static/.test(src) ) { + warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace and @classdesc: (line " + e.lineno + ")"); + src = unwrap(src); + src = src.replace(/@class/, "@classdesc").replace(/@static/, "@namespace"); + src = wrap(src); + //console.log(src); + } + + return src; + +} + +// ---- other functionality --------------------------------------------------------------------------- + +// HACK: override cli.exit() to avoid that JSDoc3 exits the VM +if ( pluginConfig.noExit ) { + info("disabling exit() call"); + require( path.join(env.dirname, 'cli') ).exit = function(retval) { + info(`cli.exit(): do nothing (ret val=${retval})`); + }; +} + + +// ---- exports ---------------------------------------------------------------------------------------- + +exports.defineTags = function(dictionary) { + + /** + * a special value that is not 'falsy' but results in an empty string when output + * Used for the disclaimer and experimental tag + */ + const EMPTY = { + toString: function() { return ""; } + }; + + /** + * Override the built-in `since` tag (with the same configuration) to check that + * `since` and `ui5-experimental-since` are not used both for the same entity. + * + * Also see the checks in experimental and ui5-experimental-since. + */ + dictionary.defineTag("since", { + mustHaveValue: true, + onTagged: function(doclet, tag) { + if (isSemVer(doclet.experimental)) { + error(`@since and @ui5-experimental-since must not be used both for the same API (${doclet.meta?.lineno})`); + } + doclet.since = tag.value; + } + }); + + /** + * A UI5 custom tag to add a disclaimer to a symbol. + */ + dictionary.defineTag('disclaimer', { + // value is optional + onTagged: function(doclet, tag) { + doclet.disclaimer = tag.value || EMPTY; + } + }); + + /** + * A UI5 custom tag to mark a symbol as experimental. + * + * Note: `experimental` and `ui5-experimental-since` must not used be used within the same doclet. + * + * @deprecated As of version 1.139, use @ui5-experimental-since + */ + dictionary.defineTag('experimental', { + // value is optional + onTagged: function(doclet, tag) { + future(`As of version 1.139, the experimental tag is deprecated and should no longer be used. Use the @ui5-experimental-since tag instead (${doclet.meta?.lineno})`); + if (isSemVer(doclet.experimental)) { + error(`@experimental and @ui5-experimental-since must not be used both for the same API (${doclet.meta?.lineno})`); + } + const value = tag.value?.trim(); + if (isSemVer(value)) { + // In some places, @experimental was used with a version only. + // Prefix the value to avoid misinterpretation as @ui5-experimental-since + doclet.experimental = `As of version ${value}`; + } else { + doclet.experimental = value || EMPTY; + } + } + }); + + /** + * A UI5 custom tag to mark a symbol as experimental. + * + * This tag only allows and expects a since version, no additional text. + * + * Note: `experimental` and `ui5-experimental-since` must not used be used within the same doclet. + */ + dictionary.defineTag('ui5-experimental-since', { + mustHaveValue: true, + onTagged: function(doclet, tag) { + if (doclet.since || doclet.experimental) { + error(`@experimental/@since and @ui5-experimental-since must not be used together for the same API (${doclet.meta?.lineno})`); + } + const version = tag.value?.trim() ?? ""; + if (!isSemVer(version)) { + error(`@ui5-experimental-since must have a version and only a version, not '${version}' (${doclet.meta?.lineno})`); + } + doclet.experimental = version; + } + }); + + /** + * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, but we use it to mark classes as final + */ + dictionary.defineTag('final', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.final_ = true; + } + }); + + /** + * Introduce a new kind of symbol: 'interface' + * 'interface' is like 'class', but without a constructor. + * Support for 'interface' might not be complete (only standard UI5 use cases tested) + */ + dictionary.defineTag('interface', { + //mustNotHaveValue: true, + onTagged: function(doclet, tag) { + // debug("setting kind of " + doclet.name + " to 'interface'"); + doclet.kind = 'interface'; + if ( tag.value ) { + doclet.classdesc = tag.value; + } + } + }); + + /** + * Classes can declare that they implement a set of interfaces. + */ + dictionary.defineTag('implements', { + mustHaveValue: true, + onTagged: function(doclet, tag) { + // console.log("setting implements of " + doclet.name + " to 'interface'"); + if ( tag.value ) { + doclet.implements = doclet.implements || []; + tag.value.split(/\s*,\s*/g).forEach(($) => { + // JSDoc's own @implements expects the curly braces notation, cut them off + if ( $.startsWith("{") && $.endsWith("}") ) { + $ = $.slice(1, -1).trim(); + } + if ( $ && doclet.implements.indexOf($) < 0 ) { + doclet.implements.push($); + } + }); + } + } + }); + + /** + * Set the visibility of a doclet to 'restricted'. + */ + dictionary.defineTag('ui5-restricted', { + onTagged: function(doclet, tag) { + doclet.access = 'restricted'; + if ( tag.value ) { + ui5data(doclet).stakeholders = tag.value.trim().split(/\s*,\s*/); + } + } + }); + /** + * @deprecated Use `ui5-restricted` instead. + */ + dictionary.defineTag('sap-restricted', { + onTagged: function(doclet, tag) { + error("Tag @sap-restricted has been deprecated, use @ui5-restricted instead"); + doclet.access = 'restricted'; + if ( tag.value ) { + ui5data(doclet).stakeholders = tag.value.trim().split(/\s*,\s*/); + } + } + }); + dictionary.defineTag('ui5-omissible-params', { + onTagged: function(doclet, tag) { + if ( tag.value ) { + ui5data(doclet).omissibleParams = tag.value.trim().split(/\s*,\s*/); + } + } + }); + dictionary.defineTag('ui5-module-override', { + onTagged: function(doclet, tag) { + if ( tag.value ) { + const args = tag.value.trim().split(/\s+/); + ui5data(doclet).moduleOverride = [ args[0], args[1] ?? ""]; + } + } + }); + /** + * Mark a doclet as synthetic. + * + * Used for doclets that the autodoc generation creates. This helps the template + * later to recognize such doclets and maybe filter them out. + */ + dictionary.defineTag('synthetic', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.synthetic = true; + } + }); + + /** + * Mark a doclet that intentionally updates a previous doclet + */ + dictionary.defineTag('ui5-updated-doclet', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + ui5data(doclet).updatedDoclet = true; + } + }); + + /** + * The @hideconstructor tag tells JSDoc that the generated documentation should not display the constructor for a class. + * Note: this tag will be natively available in JSDoc >= 3.5.0 + */ + dictionary.defineTag('hideconstructor', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.hideconstructor = true; + } + }); + + /** + * A first-class member with this tag has no module export. + */ + dictionary.defineTag("ui5-global-only", { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + ui5data(doclet).globalOnly = true; + } + }); + + /** + * An entity with this tag should be marked with ts-ignore in TypeScript definitions. + * To keep the ignored sections small, only methods and properties will honor this tag. + * For all other entities, it will be ignored. + */ + dictionary.defineTag("ts-skip", { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + ui5data(doclet).tsSkip = true; + } + }); + + /** + * The 'template' can be used to describe type parameters of generic functions. + * + * - the 'name' part of the tag is mandatory + * - the 'type' part defines any type constraints + * - a type parameter can have a default value (default type) + * + * Examples: + *
    +	 *   @template E - Element type, just a name ()
    +	 *   @template {UI5Element} E - Element type with type constraint ()
    +	 *   @template {UI5Element} [E=UI5Control] - Type param with a default type ()
    +	 * 
    + * + * Note: Defining a type constraint implicitly makes the constraint the default value of the param. + * Additionally documenting a default type is only necessary when it differs from the constraint. + * + * @see {@link https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template} + */ + dictionary.defineTag("template", { + canHaveType: true, + canHaveName: true, + onTagged(doclet, tag) { + if ( !tag.value ) { + error("@template tag must have a value ('" + tag.text + "')"); + return; + } + if ( !tag.value.name ) { + error("@template tag must have a name ('" + tag.text + "')"); + return; + } + doclet.typeParameters = doclet.typeParameters || []; + doclet.typeParameters.push(tag.value); + } + }); +}; + +exports.handlers = { + + /** + * Before all files are parsed, determine the common path prefix of all filenames + * @param {object} e Event info object + */ + parseBegin : function(e) { + + pathPrefixes = env.opts._.reduce((result, fileOrDir) => { + fileOrDir = path.resolve( path.normalize(fileOrDir) ); + if ( fs.statSync(fileOrDir).isDirectory() ) { + // ensure a trailing path separator + if ( fileOrDir.indexOf(path.sep, fileOrDir.length - path.sep.length) < 0 ) { + fileOrDir += path.sep; + } + result.push(fileOrDir); + } + return result; + }, []); + resourceNamePrefixes = pluginConfig.resourceNamePrefixes || []; + if ( !Array.isArray(resourceNamePrefixes) ) { + resourceNamePrefixes = [resourceNamePrefixes]; + } + resourceNamePrefixes.forEach(ensureEndingSlash); + while ( resourceNamePrefixes.length < pathPrefixes.length ) { + resourceNamePrefixes.push(''); + } + + debug("path prefixes " + JSON.stringify(pathPrefixes)); + debug("resource name prefixes " + JSON.stringify(resourceNamePrefixes)); + }, + + /** + * Log each file before it is parsed + * @param {object} e Event info object + */ + fileBegin: function (e) { + currentProgram = undefined; + currentModule = { + name: null, + resource: getResourceName(e.filename), + module: getModuleName(getResourceName(e.filename)), + localNames: Object.create(null), + localNamesByLoc: Object.create(null) + }; + }, + + fileComplete: function (e) { + // debug("module info after parsing: ", currentModule); + currentSource = undefined; + currentProgram = undefined; + currentModule = undefined; + }, + + jsdocCommentFound: function(e) { + // console.log("jsdocCommentFound: " + e.comment); + e.comment = preprocessComment(e); + }, + + /* + symbolFound: function(e) { + // console.log("symbolFound: " + e.comment); + }, + */ + + newDoclet: function(e) { + + const _ui5data = ui5data(e.doclet); + + // remove code: this is a try to reduce the required heap size + if ( e.doclet.meta ) { + if ( e.doclet.meta.code ) { + e.doclet.meta.code = {}; + } + const filepath = (e.doclet.meta.path && e.doclet.meta.path !== 'null' ) ? path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; + e.doclet.meta.__shortpath = getRelativePath(filepath); + _ui5data.resource = currentModule.resource; + _ui5data.module = currentModule.name || currentModule.module; + _ui5data.initialLongname = e.doclet.longname; + + const localDecl = findLocalDeclaration(e.doclet.meta.lineno, e.doclet.meta.columnno); + if ( localDecl ) { + debug("found matching local declaration", e.doclet.longname, "'" + localDecl.export + "'", currentModule.defaultExport); + _ui5data.export = localDecl.export; + } + if ( _ui5data.moduleOverride ) { + _ui5data.resource = _ui5data.moduleOverride[0] + ".js"; + _ui5data.module = _ui5data.moduleOverride[0]; + _ui5data.export = _ui5data.moduleOverride[1]; + } + } + + + // JSDoc 3 has a bug when it encounters a property in an object literal with an empty string as name + // (e.g. { "" : something } will result in a doclet without longname + if ( !e.doclet.longname && !e.doclet.undocumented ) { + if ( e.doclet.memberof ) { + e.doclet.longname = e.doclet.memberof + "." + e.doclet.name; // TODO '.' depends on scope? + warning(`found doclet without longname, derived longname: ${e.doclet.longname} ${location(e.doclet)}`); + } else { + future(`found doclet without longname, could not derive longname ${location(e.doclet)}`); + } + return; + } + + // try to detect misused memberof + if ( e.doclet.memberof && e.doclet.longname.indexOf(e.doclet.memberof) !== 0 ) { + warning(`potentially unsupported use of @name and @memberof ${location(e.doclet)}`); + //console.log(e.doclet); + } + + /*if ( e.doclet.returns + && e.doclet.returns.length > 0 + && e.doclet.returns[0] + && e.doclet.returns[0].type + && e.doclet.returns[0].type.names + && e.doclet.returns[0].type.names[0] === 'this' + && e.doclet.memberof ) { + warning(`fixing return type 'this' with ${e.doclet.memberof}`); + e.doclet.returns[0].type.names[0] = e.doclet.memberof; + }*/ + }, + + beforeParse : function(e) { + msgHeader("parsing " + getRelativePath(e.filename)); + currentSource = e.source; + }, + + /** + * Event `parseComplete` is fired by JSDoc after all files have been parsed, + * but before inheritance, mixins or borrows are processed. + * + * We use this event to merge our additional data into the doclets collected by JSDoc. + * The merge must happen before doclets are cloned during the prcessing of augments or borrows. + */ + parseComplete : function(e) { + const doclets = e.doclets; + const l = doclets.length; + for (let i = 0; i < l; i++) { + const doclet = doclets[i]; + + // add metadata to class symbols + let classInfo = classInfos[doclet.longname]; + if (classInfo == null && doclet.longname?.startsWith("module:")) { + classInfo = classInfos[toRuntimeMetadataName(doclet.longname)]; + } + if ( classInfo ) { + // debug("class data", doclet.longname, "'" + classInfos[doclet.longname].export + "'"); + if ( doclet.__ui5.export === undefined ) { + doclet.__ui5.export = classInfo.export; + } + + doclet.__ui5.metadata = classInfo; + // Push the stereotype to the main doclet.__ui5 object since that's where it's read. + doclet.__ui5.stereotype = classInfo.stereotype; + + // add designtime infos, if configured + let designtimeModule = doclet.__ui5.metadata.designtime; + if ( designtimeModule && typeof designtimeModule !== 'string' ) { + designtimeModule = doclet.__ui5.module + ".designtime"; + } + if ( designtimeModule && designtimeInfos[designtimeModule] ) { + info(`associating designtime data with class metadata: ${designtimeModule}`); + // TODO do a more generic merge or maybe add whole information as "designtime" information + doclet.__ui5.metadata.annotations = designtimeInfos[designtimeModule].annotations; + } + + // derive extends from UI5 APIs + if ( doclet.__ui5.metadata.baseType + && !(doclet.augments && doclet.augments.length > 0) ) { + doclet.augments = doclet.augments || []; + info(` @extends ${doclet.__ui5.metadata.baseType} derived from UI5 APIs (${doclet.longname})`); + doclet.augments.push(doclet.__ui5.metadata.baseType); + } + + // derive interface implementations from UI5 metadata + if ( doclet.__ui5.metadata.interfaces && doclet.__ui5.metadata.interfaces.length ) { + /* eslint-disable no-loop-func */ + doclet.__ui5.metadata.interfaces.forEach((rtmIntf) => { + doclet.implements = doclet.implements || []; + // add an interface only if no "matching" JSDoc interface is present already + + // TODO if the JSDoc name and runtime metadata name of an interface differ, + // this today requires redundant documentation. Ideally, the JSDoc plugin + // should handle this and convert the runtime metadata name to the JSDoc name. + // Unfortunately, at this processing step here, the necessary information is + // not yet available (external APIs are loaded only later). + if ( doclet.implements.every((jsdocIntf) => toRuntimeMetadataName(jsdocIntf) !== rtmIntf)) { + info(` @implements ${rtmIntf} derived from UI5 metadata (${doclet.longname})`); + doclet.implements.push(rtmIntf); + } + }); + /* eslint-enable no-loop-func */ + } + } + + // add DataType info to typedef symbols + let typeInfo = typeInfos[doclet.longname]; + if (typeInfo == null && doclet.longname?.startsWith("module:")) { + typeInfo = typeInfos[toRuntimeMetadataName(doclet.longname)]; + } + if ( typeInfo ) { + doclet.__ui5.stereotype = 'datatype'; + doclet.__ui5.metadata = { + basetype: typeInfo.base, + pattern: typeInfo.pattern, + range: typeInfo.range + }; + } + + // add enum values to enum keys (for enum symbols) + if ( (doclet.kind === 'member' || doclet.kind === 'constant') && doclet.isEnum && Array.isArray(doclet.properties) ) { + // determine unique enum identifier from key set + let enumID = doclet.properties.map(function(prop) { + return prop.name; + }).sort().join("|"); + enumID += "||" + ui5data(doclet).resource; // build almost-unique key + if ( enumValues[enumID] ) { + // debug("found enum values for ", enumID, enumValues[enumID]); + let standardEnum = true; + /* eslint-disable no-loop-func */ + doclet.properties.forEach((prop) => { + prop.__ui5.value = enumValues[enumID][prop.name]; + if ( prop.__ui5.value !== prop.name ) { + standardEnum = false; + } + }); + /* eslint-enable no-loop-func */ + if ( standardEnum ) { + doclet.__ui5.stereotype = 'enum'; + } + } + } + } + }, + + /** + * Event `processingComplete` is fired by JSDoc after all files have been parsed, + * and after inheritance, mixins and borrows have been processed. + * + * The `e.doclets` contains the symbols that will be given to templates for publishing. + * + * We use this event to remove symbols that are not of interest: + * - undocumented When JSDoc finds a class, function, object or member without a + * JSDoc comment, it creates a doclet with a truthy `undocumented` + * property + * - ignore A symbol that has been marked with `@ignore` in the source code + * - anonymous JSDoc could not infer a name for the symbol, neither from source + * code nor from JSDoc comments + * - local Local entities (e.g. local vars) can't be addressed from the outside + * and therefore are generally not considered as API in UI5 + * - duplicates This plugin generates doclets for the accessor methods of + * managed properties, aggregations, events, associations. + * Developers might have created JSDoc comments for the same methods, + * either because they have overridden them in code or because they + * wanted to detail the method contract. If such duplicate doclets + * are detected, the developer created doclets are preferred. If + * multiple developer created doclets for the same entity exist in the + * same file, the last one wins. If multiple doclets exists across + * files, the one created last wins (but usually, this indicates a + * copy & paste error) + * + * The cleanup is done in `processingComplete` as the processing of `@augments` or + * `@borrows` tags might have created new non-interesting symbols. + */ + processingComplete(e) { + const doclets = e.doclets; + + // sort doclets by name, synthetic, lineno, uid for easier detection of duplicates + debug("sorting doclets by name"); + doclets.sort((a, b) => { + if ( a.longname === b.longname ) { + if ( a.synthetic === b.synthetic ) { + if ( a.meta && b.meta && a.meta.filename === b.meta.filename ) { + if ( a.meta.lineno !== b.meta.lineno ) { + return a.meta.lineno < b.meta.lineno ? -1 : 1; + } + } + return a.__ui5.id - b.__ui5.id; + } + return a.synthetic && !b.synthetic ? -1 : 1; + } + return a.longname < b.longname ? -1 : 1; + }); + debug("sorting doclets by name done."); + + // cleanup doclets + const rAnonymous = /^(~|$)/; + const l = doclets.length; + let j = 0; + for (let i = 0; i < l; i++) { + const doclet = doclets[i]; + + // skip undocumented, ignored, anonymous entities as well as local entities + if (doclet.undocumented + || doclet.ignore + || (doclet.memberof && rAnonymous.test(doclet.memberof)) + || doclet.longname.includes("~") ) { + continue; + } + + // check for duplicates: last one wins + if ( j > 0 && doclets[j - 1].longname === doclet.longname ) { + if ( !doclets[j - 1].synthetic && !doclet.__ui5.updatedDoclet ) { + // replacing synthetic comments or updating comments are trivial cases. Just log non-trivial duplicates + debug(`ignoring duplicate doclet for ${doclet.longname}: ${location(doclet)} overrides ${location(doclets[j - 1])}`); + } + doclets[j - 1] = doclet; + continue; + } + + doclets[j++] = doclet; + } + + if ( j < l ) { + doclets.splice(j, l - j); + info(`processingComplete: removed ${l - j} undocumented, ignored, anonymous, local or duplicate symbols - ${doclets.length} remaining`); + } + + if ( pluginConfig.saveSymbols ) { + fs.mkPath(env.opts.destination); + fs.writeFileSync(path.join(env.opts.destination, "symbols-processingComplete.json"), JSON.stringify(e.doclets, null, "\t"), 'utf8'); + } + } +}; + +exports.astNodeVisitor = { + + visitNode: function(node, e, parser, currentSourceName) { + + if ( node.type === Syntax.Program ) { + currentProgram = node; + scopeManager = escope.analyze(currentProgram); + } + + function processExtendCall(extendCall, comment, commentAlreadyProcessed) { + const doclet = comment && new Doclet(getRawComment(comment), {}); + const classInfo = collectClassInfo(extendCall, doclet); + if ( classInfo ) { + createAutoDoc(classInfo, comment, doclet, extendCall, parser, currentSourceName, commentAlreadyProcessed); + } + } + + function processDataType(createCall, comment) { + const doclet = comment && new Doclet(getRawComment(comment), {}); + const typeInfo = collectDataTypeInfo(createCall, doclet); + if ( typeInfo ) { + createDataTypeAutoDoc(typeInfo, comment, createCall, parser, currentSourceName); + } + } + + function processPotentialEnum(literal, comment) { + const values = literal.properties.reduce((map, prop) => { + map[getPropertyKey(prop)] = convertValue(prop.value); + return map; + }, Object.create(null)); + // determine unique enum ID from key set + let enumID = Object.keys(values).sort().join("|"); + if (enumID.length) { // many false positives have no values at all, ignore them + enumID += "||" + currentModule.resource; // make the key "really almost" unique + // and remember the values with that ID + enumValues[enumID] = values; + // debug("found enum values for key-set", enumID); + } + } + + if ( [Syntax.ExpressionStatement, Syntax.LogicalExpression].includes(node.type) ) { + let nodeToAnalyze; + if (isSapUiDefineCall(node.expression)) { + nodeToAnalyze = node.expression; + + /* + } else if ( isJQuerySapDeclareCall(node.expression) + && node.expression.arguments.length > 0 + && node.expression.arguments[0].type === Syntax.Literal + && typeof node.expression.arguments[0].value === "string" ) { + warning(`module has explicit module name ${node.expression.arguments[0].value}`); + */ + } else if (isSapUiDefineCall(node.left)) { + nodeToAnalyze = node.left; + } else if (isSapUiDefineCall(node.right)) { + nodeToAnalyze = node.right; + } + + if (nodeToAnalyze) { + analyzeModuleDefinition( + resolvePotentialWrapperExpression(nodeToAnalyze) + ); + } + } + + const isArrowExpression = isArrowFuncExpression(node) && node.body.type === Syntax.ObjectExpression; + const nodeArgument = isArrowExpression + ? node.body + : resolvePotentialWrapperExpression(node).argument; + if (isArrowExpression || (isReturningNode(node) && nodeArgument && nodeArgument.type === Syntax.ObjectExpression) && /\.designtime\.js$/.test(currentSourceName) ) { + + // assume this node to return designtime metadata. Collect it and remember it by its module name + const oDesigntimeInfo = collectDesigntimeInfo(nodeArgument); + if ( oDesigntimeInfo ) { + designtimeInfos[currentModule.module] = oDesigntimeInfo; + info(`collected designtime info ${currentModule.module}`); + } + + } else if ( node.type === Syntax.ExpressionStatement && isExtendCall(node.expression) ) { + + // Something.extend(...) -- return value (new class) is not used in an assignment + + // className = node.expression.arguments[0].value; + const comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + // console.log("ast node with comment " + comment); + processExtendCall(node.expression, comment); + + } else if ( node.type === Syntax.VariableDeclaration ) { + node.declarations.forEach((decl, idx) => { + if ( isExtendCall(decl.init) ) { + // var NewClass = Something.extend(...) + + // className = node.declarations[0].init.arguments[0].value; + const comment = (idx === 0 ? getLeadingCommentNode(node) : undefined) || getLeadingCommentNode(decl); + // console.log(`ast node with comment ${comment}`); + processExtendCall(decl.init, comment); + } else if ( isPotentialEnum(decl.init) ) { + const comment = (idx === 0 ? getLeadingCommentNode(node) : undefined) || getLeadingCommentNode(decl); + processPotentialEnum(decl.init, comment); + } + }); + + } else if ( isReturningNode(node) + && isExtendCall(resolvePotentialWrapperExpression(node).argument || node.body) ) { + + const nodeArgument = isArrowFuncExpression(node) + ? node.body + : resolvePotentialWrapperExpression(node).argument; + + // return Something.extend(...) + + const className = convertValue(nodeArgument.arguments[0]); + const comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(nodeArgument, className); + // console.log(`ast node with comment ${comment}`); + processExtendCall(nodeArgument, comment, true); + } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression ) { + + if ( isCreateDataTypeCall(node.expression.right) ) { + + // thisLib.TypeName = DataType.createType( ... ) + const comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + processDataType(node.expression.right, comment); + // TODO remember knowledge about type and its name (left hand side of assignment) + + } else if ( isPotentialEnum(node.expression.right) ) { + const comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + // console.log(getResolvedObjectName(node.expression.left)); + processPotentialEnum(node.expression.right, comment); + } + + } else if ( node.type === Syntax.File ) { + // Check for copyright notice + + let programBodyStart; + if ( node.program.body.length >= 1 ) { + programBodyStart = node.program.body[0].start; + } else { + // File has no code at all + programBodyStart = Infinity; + } + + const hasCopyrightComment = ( node.comments || [] ).some(function(commentBlock) { + return ( + // Copyright comments must be at the top of the file, not between some code or at the end + commentBlock.end <= programBodyStart && + /copyright|\(c\)|released under|license|\u00a9/.test(commentBlock.value) + ); + }); + if ( !hasCopyrightComment ) { + error(`document doesn't contain a copyright notice: ${getResourceName(currentSourceName)}`); + } + } + } + +}; + +(function() { + const jsdocType = require("jsdoc/lib/jsdoc/tag/type"); + const catharsis = require('catharsis'); + const TYPES = catharsis.Types; + + const toTypeString = (type) => getTypeStrings(type).join("|"); + + /* + * This function has been copied from jsdoc/lib/jsdoc/tag/type (version 3.6.7) + * The copy has been enhanced with the changes from https://github.com/jsdoc/jsdoc/pull/1735 + * to retain the full function signature for function types and with a further change + * to retain the full record type structure. + * + * JSDoc is copyright (c) 2011-present Michael Mathews micmath@gmail.com and the contributors to JSDoc. + */ + function getTypeStrings(parsedType, isOutermostType) { + let applications; + let typeString; + let paramTypes; + let types = []; + switch (parsedType.type) { + case TYPES.AllLiteral: + types.push('*'); + break; + case TYPES.FunctionType: + typeString = 'function'; + // #### BEGIN: MODIFIED BY SAP + paramTypes = []; + // add only one of 'new:' or 'this:' type, 'new:' is preferred as it implies a 'this:' + if (parsedType.new) { + paramTypes.push("new:" + toTypeString(parsedType.new)); + } else if (parsedType.this) { + paramTypes.push("this:" + toTypeString(parsedType.this)); + } + + if (Array.isArray(parsedType.params)) { + paramTypes.push(...parsedType.params.map((paramType) => { + return toTypeString(paramType) + (paramType.optional ? "=" : ""); + })); + } + if (paramTypes.length || parsedType.result) { + typeString += `(${paramTypes.join(", ")})`; + } + if (parsedType.result) { + let resultType = toTypeString(parsedType.result); + // ensure the function result remains belonging together even when a union of the entire function with other types is created + // Example: ensure parentheses around function result in: function(int):({rows: int, columns: int}|null)|undefined + if (parsedType.result.type === "TypeUnion") { + resultType = `(${resultType})`; + } + typeString += `:${resultType}`; + } + types.push(typeString); + // #### END: MODIFIED BY SAP + break; + case TYPES.NameExpression: + types.push(parsedType.name); + break; + case TYPES.NullLiteral: + types.push('null'); + break; + case TYPES.RecordType: + // #### BEGIN: MODIFIED BY SAP + // types.push('Object'); + if (Array.isArray(parsedType.fields)) { + typeString = `{${parsedType.fields.map( + ({key,value}) => { + const keyString = catharsis.stringify(key); + if (value) { + var propertyPlusType = `${keyString}: ${toTypeString(value)}`; + return propertyPlusType; + } else { + let pos; + if (keyString && (pos = keyString.indexOf(":")) > -1) { + // When no space is present between colon and type (e.g. the structure looks like "{x:number}" with no space after + // the colon), then Catharsis parses this as property name "x:number" with no type given. + // In this case give a clear hint that this space is needed and throw an error to prevent such issues from being merged + const realName = keyString.substring(0, pos); + const realValue = keyString.substring(pos + 1, keyString.length); + let message = `Cannot parse the "${keyString}" part of "${parsedType.typeExpression}" in RecordType (log output above may give a hint in which file).\n`; + message += `Did you mean to specify a property "${realName}" of type "${realValue}"? Then insert a space after the colon and write "${realName}: ${realValue}".`; + error(message); + return "x: any"; // unparseable property set to "any" - but the JSDoc run will fail now, anyway + } else { + // only property given without type; this will be turned into type "any" further downstream + return keyString; + } + } + } + ).join(', ')}}`; + types.push(typeString); + } else { + types.push('Object'); + } + // #### END: MODIFIED BY SAP + break; + case TYPES.TypeApplication: + // if this is the outermost type, we strip the modifiers; otherwise, we keep them + if (isOutermostType) { + applications = parsedType.applications.map((application) => + catharsis.stringify(application)).join(', '); + typeString = `${getTypeStrings(parsedType.expression)[0]}.<${applications}>`; + types.push(typeString); + } else { + types.push( catharsis.stringify(parsedType) ); + } + break; + case TYPES.TypeUnion: + parsedType.elements.forEach((element) => { + types = types.concat( getTypeStrings(element) ); + }); + break; + case TYPES.UndefinedLiteral: + types.push('undefined'); + break; + case TYPES.UnknownLiteral: + types.push('?'); + break; + default: + // this shouldn't happen + throw new Error(`unrecognized type ${parsedType.type} in parsed type: ${parsedType}`); + } + return types; + } + + const origParse = jsdocType.parse; + jsdocType.parse = function() { + const tagInfo = origParse.apply(this, arguments); + // #### BEGIN: MODIFIED BY SAP + if ( tagInfo && (/function/.test(tagInfo.typeExpression) || /\{.*\}/.test(tagInfo.typeExpression)) && tagInfo.parsedType ) { + // #### END: MODIFIED BY SAP + // console.info("old typeExpression", tagInfo.typeExpression); + // console.info("old parse tree", tagInfo.parsedType); + // console.info("old parse result", tagInfo.type); + tagInfo.type = getTypeStrings(tagInfo.parsedType); + // console.info("new parse result", tagInfo.type); + } + return tagInfo; + }; +}()); diff --git a/packages/builder/lib/processors/jsdoc/lib/ui5/template/publish.js b/packages/builder/lib/processors/jsdoc/lib/ui5/template/publish.js new file mode 100644 index 00000000000..02f3d26b822 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/ui5/template/publish.js @@ -0,0 +1,3509 @@ +/* + * JSDoc3 template for UI5 documentation generation. + * + * (c) Copyright 2025 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +/* global env */ + +"use strict"; + +/* imports */ +const helper = require('jsdoc/util/templateHelper'); +const fs = require('jsdoc/fs'); +const doclet = require('jsdoc/doclet'); +const path = require('jsdoc/path'); +const logger = require('jsdoc/util/logger'); +// const catharsis = require('catharsis'); + +/* ---- logging ---- */ + +const debug = logger.debug.bind(logger); +const info = logger.info.bind(logger); +const warning = logger.warn.bind(logger); +const error = logger.error.bind(logger); + +const {extractVersion, extractSince} = require("./utils/versionUtil"); +const {ASTBuilder, TypeParser} = require("./utils/typeParser"); + +/* errors that might fail the build in future */ +function future(msg) { + if ( logger.getLevel() >= logger.LEVELS.WARN ) { + const args = Array.prototype.slice.apply(arguments); + args[0] = "FUTURE: " + args[0] + " (future error, ignored for now)"; + /* eslint-disable no-console */ + console.warn.apply(console, args); + /* eslint-disable no-console */ + } +} + +/* globals, constants */ +const MY_TEMPLATE_NAME = "ui5", + MY_ALT_TEMPLATE_NAME = "sapui5-jsdoc3", + ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME; + +const templatesConf = (env.conf.templates || {}), + templateConf = templatesConf[MY_TEMPLATE_NAME] || templatesConf[MY_ALT_TEMPLATE_NAME] || {}; + +let conf = {}; + +let __symbols; +let __longnames; +const __missingLongnames = {}; + +function merge(target, source) { + if ( source != null ) { + // simple single source merge + Object.keys(source).forEach((prop) => { + // guarding against prototype pollution. (https://codeql.github.com/codeql-query-help/javascript/js-prototype-pollution-utility/#example) + if (prop === "__proto__" || prop === "constructor") { + return; + } + const value = source[prop]; + if ( value != null && value.constructor === Object ) { + merge(target[prop] || {}, value); + } else { + target[prop] = value; + } + }); + } + // if there are more sources, merge them, too + for (let i = 2; i < arguments.length; i++) { + merge(target, arguments[i]); + } + return target; +} + +function lookup(key) { + if ( !Object.hasOwn(__longnames, key) ) { + __missingLongnames[key] = (__missingLongnames[key] || 0) + 1; + __longnames[key] = __symbols.find((symbol) => symbol.longname === key); + } + return __longnames[key]; +} + +function createSymbol(longname, lines = []) { + const comment = [ + "@name " + longname, + ...lines + ]; + + const symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); + symbol.__ui5 = {}; + + __longnames[longname] = symbol; + __symbols.push(symbol); + + return symbol; +} + +const externalSymbols = {}; + +function loadExternalSymbols(apiJsonFolder) { + + let files; + + try { + files = fs.readdirSync(templateConf.apiJsonFolder); + } catch (e) { + if ( e.code === "ENOENT" ) { + warning("folder with external symbols does not exist (ignored)"); + } else { + error(`failed to list symbol files in folder '${apiJsonFolder}': ${e.message || e}`); + } + return; + } + + if ( files && files.length ) { + files.forEach((localFileName) => { + const file = path.join(templateConf.apiJsonFolder, localFileName); + try { + const sJSON = fs.readFileSync(file, 'UTF-8'); + const data = JSON.parse(sJSON); + if ( !Array.isArray(data.symbols) ) { + throw new TypeError("api.json does not contain a 'symbols' array"); + } + data.symbols.forEach((symbol) => { + debug(` adding external symbol ${symbol.name}`); + externalSymbols[symbol.name] = symbol; + }); + } catch (e) { + error(`failed to load symbols from ${file}: ${e.message || e}`); + } + }); + } +} + +function isModuleExport($) { + return $.longname.startsWith("module:") && $.longname.search(/[.#~]/) < 0; +} + +function isaClass($) { + return /^(namespace|interface|class|typedef)$/.test($.kind) || (($.kind === 'member' || $.kind === 'constant') && $.isEnum )/* isNonEmptyNamespace($) */; +} + +function supportsInheritance($) { + return /^(interface|class|typedef)$/.test($.kind); +} + +/* + * Returns true for any symbol that should appear in the API reference index of the SDK. + * + * In a perfect world, such symbols would be + * - default exports of AMD modules (named 'module:some/module) + * - classes, interfaces, enums, typedefs and namespaces, all with global names whose parents are all namespaces + * In the less perfect documentation build, the criterion 'whose parents are all namespaces' is ignored + */ +function isFirstClassSymbol($) { + return ( + /^(namespace|interface|class|typedef)$/.test($.kind) + || ($.kind === 'member' || $.kind === 'constant') && $.isEnum + || ['function', 'member'].includes($.kind) && isModuleExport($) + )/* isNonEmptyNamespace($) */; +} + +function writeSymbols(symbols, filename, caption) { + function filter(key,value) { + if ( key === 'meta' ) { + //return; + } + if ( key === '__ui5' && value ) { + const v = { + resource: value.resource, + module: value.module, + stakeholders: value.stakeholders, + globalOnly: value.globalOnly + }; + if ( value.derived ) { + v.derived = value.derived.map(($) => $.longname); + } + if ( value.base ) { + v.base = value.base.longname; + } + if ( value.implementations ) { + v.base = value.implementations.map(($) => $.longname); + } + if ( value.parent ) { + v.parent = value.parent.longname; + } + if ( value.children ) { + v.children = value.children.map(($) => $.longname); + } + return v; + } + return value; + } + if ( symbols.length < 20000 ) { + const symbolsFile = path.join(env.opts.destination, filename); + info(`writing ${caption} to ${symbolsFile}`); + fs.writeFileSync(symbolsFile, JSON.stringify(symbols, filter, "\t"), 'utf8'); + } +} +// ---- publish() - main entry point for JSDoc templates ------------------------------------------------------------------------------------------------------- + +/* Called automatically by JsDoc Toolkit. */ +function publish(symbolSet) { + + info("entering sapui5 template"); + + // create output dir + fs.mkPath(env.opts.destination); + + const originalSymbols = symbolSet().get(); + // writeSymbols(originalSymbols, "symbols-unpruned-ui5.json", "raw symbols before prune"); + + info(`before prune: ${originalSymbols.length} symbols.`); + symbolSet = helper.prune(symbolSet); + // get an array version of the symbol set, useful for filtering + const allSymbols = __symbols = symbolSet().get(); + info(`after prune: ${allSymbols.length} symbols.`); + + __longnames = {}; + allSymbols.forEach(function($) { + __longnames[$.longname] = $; + }); + + if ( templateConf.apiJsonFolder ) { + info(`loading external apis from folder '${templateConf.apiJsonFolder}'`); + loadExternalSymbols(templateConf.apiJsonFolder); + } + + // now resolve relationships + const aRootNamespaces = createNamespaceTree(allSymbols); + createInheritanceTree(allSymbols); + collectMembers(allSymbols); + mergeEventDocumentation(allSymbols); + + writeSymbols(allSymbols, "symbols-pruned-ui5.json", "raw symbols after prune"); + + // ----- + + const PUBLISHING_VARIANTS = { + + "apixml" : { + defaults : { + apiXmlFile: path.join(env.opts.destination, "jsapi.xml") + }, + processor : function(conf) { + createAPIXML(allSymbols, conf.apiXmlFile, { + legacyContent: true + }); + } + }, + + "apijson" : { + defaults : { + apiJsonFile: path.join(env.opts.destination, "api.json") + }, + processor : function(conf) { + createAPIJSON(allSymbols, conf.apiJsonFile); + } + }, + + "fullapixml" : { + defaults : { + fullXmlFile: path.join(env.opts.destination, "fulljsapi.xml") + }, + processor : function(conf) { + createAPIXML(allSymbols, conf.fullXmlFile, { + roots: aRootNamespaces, + omitDefaults : conf.omitDefaultsInFullXml, + resolveInheritance: true + }); + } + } + + }; + + info("start publishing"); + if ( Array.isArray(templateConf.variants) ) { + templateConf.variants.forEach((vVariant) => { + + if ( typeof vVariant === "string" ) { + vVariant = { variant : vVariant }; + } + + info(""); + + if ( PUBLISHING_VARIANTS[vVariant.variant] ) { + + // Merge different sources of configuration (listed in increasing priority order - last one wins) + // and expose the result in the global 'conf' variable + // - global defaults + // - defaults for current variant + // - user configuration for sapui5 template + // - user configuration for current variant + // + // Note: trailing slash expected for dirs + conf = merge({ + ext: ".html", + filter: function($) { return true; } + }, PUBLISHING_VARIANTS[vVariant.variant].defaults, templateConf, vVariant); + + info(`publishing as variant '${vVariant.variant}'`); + debug("final configuration:"); + debug(conf); + + PUBLISHING_VARIANTS[vVariant.variant].processor(conf); + + info(`done with variant '${vVariant.variant}'`); + + } else { + + info(`cannot publish unknown variant '${vVariant.variant}' (ignored)`); + + } + }); + } + + info("publishing done."); +} + +//---- namespace tree -------------------------------------------------------------------------------- + +/** + * Completes the tree of namespaces. + * + * Namespaces for which content is available but which have not been documented + * are created as dummy, public namespace with empty documentation. + * + * @param {Array} allSymbols Array of all symbols to be published + * @returns {Array} Array of all root namespaces + */ +function createNamespaceTree(allSymbols) { + + info(`create namespace tree (${allSymbols.length} symbols)`); + + const aRootNamespaces = []; + const aTypes = allSymbols.filter((symbol) => isFirstClassSymbol(symbol)); + + for (let i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications + + const symbol = aTypes[i]; + if ( symbol.memberof ) { + + let parent = lookup(symbol.memberof); + if ( !parent ) { + warning(`create missing namespace '${symbol.memberof}' (referenced by ${symbol.longname})`); + parent = makeNamespace(symbol.memberof); + aTypes.push(parent); // concurrent modification: parent will be processed later in this loop + } + symbol.__ui5.parent = parent; + parent.__ui5.children = parent.__ui5.children || []; + parent.__ui5.children.push(symbol); + + } else if ( symbol.longname !== ANONYMOUS_LONGNAME ) { + + aRootNamespaces.push(symbol); + + } + } + + return aRootNamespaces; +} + +function makeNamespace(memberof) { + + info(`adding synthetic namespace symbol ${memberof}`); + + return createSymbol(memberof, [ + "@namespace", + "@synthetic", + "@public" + ]); +} + +//---- inheritance hierarchy ---------------------------------------------------------------------------- + +/* + * Calculates the inheritance hierarchy for all class/interface/namespace symbols. + * Each node in the tree has the content + * + * Node : { + * longname : {string} // name of the node (usually equals symbol.longname) + * symbol : {Symbol} // backlink to the original symbol + * base : {Node} // parent node or undefined for root nodes + * derived : {Node[]} // subclasses/-types + * } + * + * @param {Array} allSymbols Array of all symbols to be published + * @returns {Array} Array of all root types + */ +function createInheritanceTree(allSymbols) { + + info(`create inheritance tree (${allSymbols.length} symbols)`); + + const aTypes = allSymbols.filter((symbol) => supportsInheritance(symbol)); + const aRootTypes = []; + + let oObject = lookup("Object"); + if ( !oObject ) { + oObject = createSymbol("Object", [ + "@class", + "@synthetic", + "@public" + ]); + aRootTypes.push(oObject); + } + + function getOrCreateClass(sClass, sExtendingClass) { + let oClass = lookup(sClass); + if ( !oClass ) { + let sKind = "class", + sBaseClass = 'Object', + sVisibility = "public"; + if ( externalSymbols[sClass] ) { + sKind = externalSymbols[sClass].kind || sKind; + sBaseClass = externalSymbols[sClass].extends || sBaseClass; + sVisibility = externalSymbols[sClass].visibility || sVisibility; + debug(`create doclet for external class ${sClass} (extended by ${sExtendingClass})`); + } else { + warning(`create missing class ${sClass} (extended by ${sExtendingClass})`); + } + const oBaseClass = getOrCreateClass(sBaseClass, sClass); + oClass = createSymbol(sClass, [ + "@extends " + sBaseClass, + "@" + sKind, + "@synthetic", + sVisibility === "restricted" ? "@ui5-restricted" : "@" + sVisibility + ]); + oClass.__ui5.base = oBaseClass; + oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; + oBaseClass.__ui5.derived.push(oClass); + } + return oClass; + } + + // link them according to the inheritance infos + aTypes.forEach((oClass) => { + + if ( oClass.longname === 'Object') { + return; + } + + let sBaseClass = "Object"; + if ( oClass.augments && oClass.augments.length > 0 ) { + if ( oClass.augments.length > 1 ) { + warning(`multiple inheritance detected in ${oClass.longname}`); + } + sBaseClass = oClass.augments[0]; + } else { + aRootTypes.push(oClass); + } + + const oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); + oClass.__ui5.base = oBaseClass; + oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; + oBaseClass.__ui5.derived.push(oClass); + + if ( oClass.implements ) { + for (let j = 0; j < oClass.implements.length; j++) { + let oInterface = lookup(oClass.implements[j]); + if ( !oInterface ) { + let sVisibility = "public"; + if ( externalSymbols[oClass.implements[j]] ) { + sVisibility = externalSymbols[oClass.implements[j]] || sVisibility; + debug(`create doclet for external interface ${oClass.implements[j]}`); + } else { + warning(`create missing interface ${oClass.implements[j]}`); + } + oInterface = createSymbol(oClass.implements[j], [ + "@interface", + "@synthetic", + sVisibility === "restricted" ? "@ui5-restricted" : "@" + sVisibility + ]); + oInterface.__ui5.base = oObject; + oObject.__ui5.derived = oObject.__ui5.derived || []; + oObject.__ui5.derived.push(oInterface); + } + oInterface.__ui5.implementations = oInterface.__ui5.implementations || []; + oInterface.__ui5.implementations.push(oClass); + } + } + }); + + function setStereotype(oSymbol, sStereotype) { + if ( !oSymbol ) { + return; + } + oSymbol.__ui5.stereotype = sStereotype; + const derived = oSymbol.__ui5.derived; + if ( derived ) { + for (let i = 0; i < derived.length; i++ ) { + if ( !derived[i].__ui5.stereotype ) { + setStereotype(derived[i], sStereotype); + } + } + } + } + + setStereotype(lookup("sap.ui.core.Component"), "component"); + setStereotype(lookup("sap.ui.core.Control"), "control"); + setStereotype(lookup("sap.ui.core.Element"), "element"); + setStereotype(lookup("sap.ui.base.Object"), "object"); + + // check for cyclic inheritance (not supported) + // Note: the check needs to run bottom up, not top down as a typical cyclic dependency never will end at the root node + aTypes.forEach((oStartClass) => { + const visited = {}; + function visit(oClass) { + if ( visited[oClass.longname] ) { + throw new Error(`cyclic inheritance detected: ${JSON.stringify(Object.keys(visited))}`); + } + if ( oClass.__ui5.base ) { + visited[oClass.longname] = true; + visit(oClass.__ui5.base); + delete visited[oClass.longname]; + } + } + visit(oStartClass); + }); + + // collect root nodes (and ignore pure packages) + return aRootTypes; +} + +/** + * Attaches each symbol to its parent ('memberof'). + * + * @param {Array} allSymbols Array of all symbols to be published + */ +function collectMembers(allSymbols) { + allSymbols.forEach(function($) { + if ( $.memberof ) { + const parent = lookup($.memberof); + if ( parent /* && supportsInheritance(parent) */ ) { + parent.__ui5.members = parent.__ui5.members || []; + parent.__ui5.members.push($); + } + } + }); +} + +/** + * Searches for JSDoc events that are also described in UI5 metadata + * and merges the parameter description from the JSDoc event into the UI5 event. + * + * @param {Array} allSymbols Array of all symbols to be published + */ +function mergeEventDocumentation(allSymbols) { + + debug("merging JSDoc event documentation into UI5 metadata"); + + const aTypes = allSymbols.filter((symbol) => isaClass(symbol)); + + aTypes.forEach((symbol) => { + + const metadata = symbol.__ui5.metadata; + const members = symbol.__ui5.members; + + if ( !metadata || !metadata.events || Object.keys(metadata.events).length <= 0 || !members ) { + return; + } + + // debug(`merging events for '${symbol.longname}'`); + members.forEach(($) => { + if ( $.kind === 'event' && !$.inherited + && ($.access === 'public' || $.access === 'protected' || $.access == null) + && metadata.events[$.name] + && Array.isArray($.params) + && !$.synthetic ) { + + const event = metadata.events[$.name]; + let modified = false; + + $.params.forEach((param) => { + const m = /^\w+\.getParameters\.(.*)$/.exec(param.name); + if ( m ) { + const pname = m[1]; + const ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); + if ( ui5param.type == null ) { + ui5param.type = listTypes(param.type); + modified = true; + } + if ( ui5param.doc == null ) { + ui5param.doc = param.description; + modified = true; + } + } + }); + + if ( modified ) { + info(` merged documentation for managed event ${symbol.longname}#${$.name}`); + } + + } + }); + + }); + +} + +// ---- publishing ----------------------------------------------------------------------- + +// ---- helper functions for the templates ---- + +function sortByAlias(a, b) { + const partsA = a.longname.split(/[.#]/); + const partsB = b.longname.split(/[.#]/); + let i = 0; + while ( i < partsA.length && i < partsB.length ) { + if ( partsA[i].toLowerCase() < partsB[i].toLowerCase() ) { + return -1; + } + if ( partsA[i].toLowerCase() > partsB[i].toLowerCase() ) { + return 1; + } + i++; + } + if ( partsA.length < partsB.length ) { + return -1; + } + if ( partsA.length > partsB.length ) { + return 1; + } + // as a last resort, try to compare the aliases case sensitive in case we have aliases that only + // differ in case like with "sap.ui.model.type" and "sap.ui.model.Type" + if ( a.longname < b.longname ) { + return -1; + } + if ( a.longname > b.longname ) { + return 1; + } + return 0; +} + +function getMembersOfKind(data, kind) { + const oResult = []; + //debug(`calculating kind ${kind} from ${data.longname}`); + //console.log(data); + let fnFilter; + switch (kind) { + case 'property': + fnFilter = function($) { + return ($.kind === 'constant' || $.kind === 'member') && !$.isEnum; + }; + break; + case 'event': + fnFilter = function($) { + return $.kind === 'event'; + }; + break; + case 'method': + fnFilter = function($) { + return $.kind === 'function'; + }; + break; + default: + // default: none + fnFilter = function($) { return false; }; + break; + } + + if ( data.__ui5.members ) { + data.__ui5.members.forEach(function($) { + if ( !$.inherited && fnFilter($) && conf.filter($) ) { + oResult.push($); + } + }); + } + + return oResult; +} + +// ---- type parsing --------------------------------------------------------------------------------------------- + +class TypeStringBuilder { + constructor() { + this.lt = "<"; + this.gt = ">"; + } + + safe(type) { + return type.needsParenthesis ? "(" + type.str + ")" : type.str; + } + literal(str) { + return { + simpleComponent: false, + str: str + }; + } + simpleType(type) { + return { + simpleComponent: type !== "*", + str: type + }; + } + array(componentType) { + if ( componentType.needsParenthesis || componentType.simpleComponent === false ) { + return { + simpleComponent: false, + str: "Array" + this.lt + this.safe(componentType) + this.gt + }; + } + return { + str: componentType.str + "[]" + }; + } + object(keyType, valueType) { + if ( keyType.synthetic ) { + return { + simpleComponent: false, + str: "Object" + this.lt + this.safe(valueType) + this.gt + }; + } + return { + simpleComponent: false, + str: "Object" + this.lt + this.safe(keyType) + "," + this.safe(valueType) + this.gt + }; + } + set(elementType) { + return { + simpleComponent: false, + str: 'Set' + this.lt + this.safe(elementType) + this.gt + }; + } + promise(fulfillmentType) { + return { + simpleComponent: false, + str: 'Promise' + this.lt + this.safe(fulfillmentType) + this.gt + }; + } + "function"(paramTypes, returnType, thisType, constructorType) { + paramTypes = paramTypes.map( + (type) => type.str + (type.optional ? "=" : "") + ); + if (constructorType != null) { + paramTypes.unshift(`new:${constructorType.str}`); + } else if (thisType != null) { + paramTypes.unshift(`this:${thisType.str}`); + } + return { + simpleComponent: false, + str: `function(${paramTypes.join(",")})${returnType ? " : " + this.safe(returnType) : ""}` + }; + } + structure(structure) { + const r = []; + for ( let fieldName in structure ) { + const typeOfField = structure[fieldName]; + // This builder is called bottom up. Therefore, an optional field + // has been encoded as a trailing "=" in typeOfField.str already. + // But for structures, the "=" must be added to the field name instead. + if ( typeOfField.str.endsWith("=") ) { + fieldName += "="; + typeOfField.str = typeOfField.str.slice(0, -1); + } + if ( typeOfField.synthetic ) { + r.push(fieldName); + } else { + r.push(fieldName + ":" + this.safe(typeOfField)); + } + } + return { + simpleComponent: false, + str: "{" + r.join(",") + "}" + }; + } + union(types) { + return { + needsParenthesis: true, + str: types.map((type) => this.safe(type)).join('|') + }; + } + synthetic(type) { + type.synthetic = true; + return type; + } + nullable(type) { + type.str = "?" + type.str; + return type; + } + mandatory(type) { + type.str = "!" + type.str; + return type; + } + optional(type) { + type.str = type.str + "="; + return type; + } + repeatable(type) { + type.str = "..." + type.str; + return type; + } + typeApplication(type, templateTypes) { + return { + simpleComponent: false, + str: this.safe(type) + this.lt + templateTypes.map((type) => this.safe(type)).join(',') + this.gt + }; + } +} + +const typeParser = new TypeParser(); +const _TEXT_BUILDER = new TypeStringBuilder(); + +/* +function testTypeParser(type) { + debug(`Type: '${type}' gives AST`); + try { + console.log(typeParser.parse(type)); + } catch (e) { + error("**** throws: " + e); + } +} + +testTypeParser("Array."); +testTypeParser("Array"); +testTypeParser("Object."); +testTypeParser("Object"); +testTypeParser("function(...string):Set"); +testTypeParser("{a:int,b,c:float,d,e}"); +*/ + +function _processTypeString(type, builder) { + if ( type && Array.isArray(type.names) ) { + type = type.names.join('|'); + } + if ( type ) { + try { + return typeParser.parse( type, builder ).str; + } catch (e) { + future(`failed to parse type string '${type}': ${e}`); + return type; + } + } + return undefined; +} + +function listTypes(type) { + return _processTypeString(type, _TEXT_BUILDER); +} + +function isArrayType(type) { + if ( type && Array.isArray(type.names) ) { + type = type.names.join('|'); + } + if ( type ) { + try { + const ast = typeParser.parse(type, new ASTBuilder()); + return ( ast.type === 'array' || (ast.type === 'union' && ast.types.some((subtype) => subtype.type === 'array')) ); + } catch (e) { + future(`failed to parse type string '${type}': ${e}`); + } + } + return false; +} + +function normalizeLF(text) { + if ( text == null ) { + return text; + } + return String(text).replace(/\r\n|\r|\n/g, "\n"); +} + +/* + * regexp to recognize important places in the text + * + * Capturing groups of the RegExp: + * group 1: begin of a pre block + * group 2: end of a pre block + * group 3: an empty line + surrounding whitespace (implicitly starts a new paragraph) + * group 4: an isolated line feed + surrounding whitespace + * + * [-------
     block -------] [---- an empty line and surrounding whitespace ----] [---- new line or whitespaces ----] */
    +const rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
    +
    +function normalizeWS(text) {
    +	if ( text == null ) {
    +		return text;
    +	}
    +
    +	let inpre = false;
    +	return normalizeLF(text).replace(rNormalizeText, (match, pre, endpre, emptyline, ws) => {
    +		if ( pre ) {
    +			inpre = true;
    +			return pre;
    +		} else if ( endpre ) {
    +			inpre = false;
    +			return endpre;
    +		} else if ( emptyline ) {
    +			return inpre ? emptyline : '\n\n';
    +		} else if ( ws ) {
    +			return inpre ? ws : ' ';
    +		}
    +		return match;
    +	});
    +
    +}
    +
    +const rStartsWithValidPropertyName = /^(?:[^'"\s.][^\s.]*|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")/;
    +
    +function isNestedPropertyName(name) {
    +	const match = rStartsWithValidPropertyName.exec(name);
    +	return match && match[0].length < name.length && name[match[0].length] === ".";
    +}
    +
    +//---- add on: API JSON -----------------------------------------------------------------
    +
    +function createAPIJSON(symbols, filename) {
    +
    +	const api = {
    +		"$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0"
    +	};
    +
    +	if ( templateConf.version ) {
    +		api.version = templateConf.version.replace(/-SNAPSHOT$/,"");
    +	}
    +	if ( templateConf.uilib ) {
    +		api.library = templateConf.uilib;
    +	}
    +
    +	api.symbols = [];
    +	// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
    +	symbols.slice().sort(sortByAlias).forEach((symbol) => {
    +		if ( isFirstClassSymbol(symbol) && !symbol.synthetic ) { // dump a symbol if it as a class symbol and if it is not a synthetic symbol
    +			try {
    +				const json = createAPIJSON4Symbol(symbol, false);
    +				api.symbols.push(json);
    +			} catch (e) {
    +				error(`failed to create api summary for ${symbol.name}`, e);
    +			}
    +		}
    +	});
    +
    +	postProcessAPIJSON(api);
    +	validateAPIJSON(api);
    +
    +	fs.mkPath(path.dirname(filename));
    +	fs.writeFileSync(filename, JSON.stringify(api), 'utf8');
    +	info(`  apiJson saved as ${filename}`);
    +}
    +
    +const GLOBAL_ONLY = {};
    +
    +function createAPIJSON4Symbol(symbol, omitDefaults) {
    +
    +	const obj = [];
    +	let curr = obj;
    +	let attribForKind = 'kind';
    +	const stack = [];
    +
    +	function isEmpty(obj) {
    +		if ( !obj ) {
    +			return true;
    +		}
    +		for (const n in obj) {
    +			if ( obj.hasOwnProperty(n) ) {
    +				return false;
    +			}
    +		}
    +		return true;
    +	}
    +
    +	// In some cases, JSDoc does not provide a basename in property symbol.name, but a partially qualified name
    +	// this function reduces this to the base name
    +	function basename(name) {
    +		if (name.startsWith("module:")) {
    +			const p = name.lastIndexOf("/");
    +			name = name.slice(p + 1);
    +		}
    +		const p = name.lastIndexOf(".");
    +		return p < 0 ? name : name.slice(p + 1);
    +	}
    +
    +	function tag(name, value, omitEmpty) {
    +
    +		if ( omitEmpty && !value ) {
    +			return;
    +		}
    +		if ( arguments.length === 1 ) { // opening tag
    +			stack.push(curr);
    +			stack.push(attribForKind);
    +			const obj = {};
    +			if ( Array.isArray(curr) ) {
    +				if ( attribForKind != null ) {
    +					obj[attribForKind] = name;
    +				}
    +				curr.push(obj);
    +			} else {
    +				curr[name] = obj;
    +			}
    +			curr = obj;
    +			attribForKind = null;
    +			return;
    +		}
    +		if ( value == null ) {
    +			curr[name] = true;
    +		} else {
    +			curr[name] = String(value);
    +		}
    +	}
    +
    +	function attrib(name, value, defaultValue, raw) {
    +		if ( omitDefaults && arguments.length >= 3 && value === defaultValue ) {
    +			return;
    +		}
    +		if ( arguments.length === 1 /* empty tag */ ) {
    +			curr[name] = true;
    +		} else {
    +			curr[name] = raw ? value : String(value);
    +		}
    +	}
    +
    +	function attribSince(since) {
    +		if ( since ) {
    +			const version = extractVersion(since);
    +			if ( version ) {
    +				attrib("since", version);
    +			} else {
    +				future(`**** since information not parsable: '${since}'`);
    +			}
    +		}
    +	}
    +
    +	function closeTag(name, noIndent) {
    +		attribForKind = stack.pop();
    +		curr  = stack.pop();
    +	}
    +
    +	function collection(name, attribForKind) {
    +		stack.push(curr);
    +		stack.push(attribForKind);
    +		// TODO only supported if this.curr was an object check or fully implement
    +		curr = curr[name] = [];
    +		attribForKind = attribForKind || null;
    +	}
    +
    +	function endCollection(name) {
    +		attribForKind = stack.pop();
    +		curr  = stack.pop();
    +	}
    +
    +	function tagWithSince(name, value) {
    +
    +		if ( !value ) {
    +			return;
    +		}
    +
    +		const info = extractSince(value);
    +
    +		tag(name);
    +		if ( info.since ) {
    +			attrib("since", info.since);
    +		}
    +		if (info.since === null) {
    +			future(`**** Failed to parse version in string '${value}'. ` +
    +				`Version might be missing or has an unexpected format.`);
    +		}
    +		if ( info.value ) {
    +			curr["text"] = normalizeWS(info.value);
    +		}
    +		closeTag(name, true);
    +
    +	}
    +
    +	function writeModuleInfo(member, symbol) {
    +		// write out resource, module and export only when the module is different from the module of the parent entity
    +		// and when the member was not cloned (e.g. because it is borrowed)
    +		var isBorrowed = member.__ui5.initialLongname !== member.longname;
    +
    +		if ( member.__ui5.resource
    +			&& member.__ui5.resource !== symbol.__ui5.resource
    +			&& !isBorrowed ) {
    +			attrib("resource", member.__ui5.resource);
    +		}
    +		if ( member.__ui5.module
    +			 && member.__ui5.module !== symbol.__ui5.module
    +			 && !isBorrowed ) {
    +			attrib("module", member.__ui5.module);
    +			attrib("export", member.__ui5.globalOnly ? GLOBAL_ONLY : member.__ui5.export, '', true);
    +		}
    +	}
    +
    +	function examples(symbol) {
    +		if ( symbol.examples && symbol.examples.length ) {
    +			collection("examples");
    +			for ( let j = 0; j < symbol.examples.length; j++) {
    +				const example = makeExample(symbol.examples[j]);
    +				tag("example");
    +				if ( example.caption ) {
    +					attrib("caption", example.caption);
    +				}
    +				attrib("text", normalizeLF(example.example));
    +				closeTag("example");
    +			}
    +			endCollection("examples");
    +		}
    +	}
    +
    +	function referencesList(symbol) {
    +		if ( symbol.see && symbol.see.length ) {
    +			curr["references"] = symbol.see.slice();
    +		}
    +	}
    +
    +	function visibility($) {
    +		if ( $.access === 'protected' ) {
    +			return "protected";
    +		} else if ( $.access === 'restricted' ) {
    +			return "restricted";
    +		} else if ( $.access === 'private' ) {
    +			return "private";
    +		} else {
    +			return "public";
    +		}
    +	}
    +
    +	function stakeholders($) {
    +		if ( $.access === 'restricted' ) {
    +			return $.__ui5.stakeholders;
    +		}
    +		return undefined;
    +	}
    +
    +	function exceptions(symbol) {
    +		let array = symbol.exceptions;
    +
    +		if ( Array.isArray(array) ) {
    +			array = array.filter((ex) =>
    +				(ex.type && listTypes(ex.type)) || (ex.description && ex.description.trim())
    +			);
    +		}
    +		if ( array == null || array.length === 0 ) {
    +			return;
    +		}
    +
    +		collection("throws");
    +		for (let j = 0; j < array.length; j++) {
    +			const exception = array[j];
    +			tag("exception");
    +			if ( exception.type !== undefined ) {
    +				attrib("type", listTypes(exception.type));
    +			}
    +			tag("description", normalizeWS(exception.description), true);
    +			closeTag("exception");
    +		}
    +		endCollection("throws");
    +	}
    +
    +	function stakeholderList(tagname, stakeholders) {
    +		if ( Array.isArray(stakeholders) && stakeholders.length > 0 ) {
    +			curr[tagname] = stakeholders.slice();
    +		}
    +	}
    +
    +	function methodList(tagname, methods) {
    +		methods = methods && Object.keys(methods).map((key) => methods[key]);
    +		if ( methods != null && methods.length > 0 ) {
    +			curr[tagname] = methods;
    +		}
    +	}
    +
    +	function interfaceList(tagname, interfaces) {
    +		if ( interfaces != null && interfaces.length > 0 ) {
    +			curr[tagname] = interfaces.slice();
    +		}
    +	}
    +
    +	function hasSettings($, visited) {
    +
    +		visited = visited || {};
    +
    +		if ( $.augments && $.augments.length > 0 ) {
    +			let baseSymbol = $.augments[0];
    +			if ( visited.hasOwnProperty(baseSymbol) ) {
    +				future(`detected cyclic inheritance when looking at ${$.longname}: ${JSON.stringify(visited)}`);
    +				return false;
    +			}
    +			visited[baseSymbol] = true;
    +			baseSymbol = lookup(baseSymbol);
    +			if ( hasSettings(baseSymbol, visited) ) {
    +				return true;
    +			}
    +		}
    +
    +		const metadata = $.__ui5.metadata;
    +		return metadata &&
    +			(
    +				!isEmpty(metadata.specialSettings)
    +				|| !isEmpty(metadata.properties)
    +				|| !isEmpty(metadata.aggregations)
    +				|| !isEmpty(metadata.associations)
    +				|| !isEmpty(metadata.annotations)
    +				|| !isEmpty(metadata.events)
    +			);
    +	}
    +
    +	function writeMetadata($) {
    +
    +		const metadata = $.__ui5.metadata;
    +		if ( !metadata ) {
    +			return;
    +		}
    +
    +		if ( metadata.library ) {
    +			// NOTE: Only adding "library" property for a validation within postProcessAPIJSON.
    +			// The property will be removed after the check as it contains redundant information.
    +			curr.library = metadata.library;
    +		}
    +
    +		if ( metadata.specialSettings && Object.keys(metadata.specialSettings).length > 0 ) {
    +			collection("specialSettings");
    +			for ( const n in metadata.specialSettings ) {
    +				const special = metadata.specialSettings[n];
    +				tag("specialSetting");
    +				attrib("name", special.name);
    +				attrib("type", special.type);
    +				attrib("visibility", special.visibility, 'public');
    +				if (special.stakeholders) {
    +					stakeholderList("allowedFor", special.stakeholders);
    +				}
    +				attribSince(special.since);
    +				tag("description", normalizeWS(special.doc), true);
    +				tagWithSince("experimental", special.experimental);
    +				tagWithSince("deprecated", special.deprecation);
    +				methodList("method", special.methods);
    +				closeTag("specialSetting");
    +			}
    +			endCollection("specialSettings");
    +		}
    +
    +		if ( metadata.properties && Object.keys(metadata.properties).length > 0 ) {
    +			collection("properties");
    +			for ( const n in metadata.properties ) {
    +				const prop = metadata.properties[n];
    +				let defaultValue = prop.defaultValue != null ? prop.defaultValue.value : null;
    +				// JSON can't transport a value of undefined, so represent it as string
    +				if ( defaultValue === undefined ) {
    +					defaultValue = String(defaultValue);
    +				}
    +				tag("property");
    +				attrib("name", prop.name);
    +				attrib("type", prop.type, 'string');
    +
    +				if (prop.dataType) {
    +					attrib("dataType", prop.dataType, 'string');
    +				}
    +
    +				attrib("defaultValue", defaultValue, null, /* raw = */true);
    +				attrib("group", prop.group, 'Misc');
    +				attrib("visibility", prop.visibility, 'public');
    +				if (prop.stakeholders) {
    +					stakeholderList("allowedFor", prop.stakeholders);
    +				}
    +
    +				attribSince(prop.since);
    +				if ( prop.bindable ) {
    +					attrib("bindable", prop.bindable, false, /* raw = */true);
    +				}
    +				tag("description", normalizeWS(prop.doc), true);
    +				tagWithSince("experimental", prop.experimental);
    +				tagWithSince("deprecated", prop.deprecation);
    +				methodList("methods", prop.methods);
    +				closeTag("property");
    +			}
    +			endCollection("properties");
    +		}
    +
    +		if ( metadata.defaultProperty ) {
    +			tag("defaultProperty", metadata.defaultProperty);
    +		}
    +
    +		if ( metadata.dnd ) {
    +			curr.dnd = metadata.dnd;
    +		}
    +
    +		if ( metadata.aggregations && Object.keys(metadata.aggregations).length > 0 ) {
    +			collection("aggregations");
    +			for ( const n in metadata.aggregations ) {
    +				const aggr = metadata.aggregations[n];
    +				tag("aggregation");
    +				attrib("name", aggr.name);
    +				attrib("singularName", aggr.singularName); // TODO omit default?
    +				attrib("type", aggr.type, 'sap.ui.core.Control');
    +				if ( aggr.altTypes ) {
    +					curr.altTypes = aggr.altTypes.slice();
    +				}
    +				if ( aggr.defaultClass ) {
    +					attrib("defaultClass", aggr.defaultClass);
    +				}
    +				attrib("cardinality", aggr.cardinality, '0..n');
    +				attrib("visibility", aggr.visibility, 'public');
    +				if (aggr.stakeholders) {
    +					stakeholderList("allowedFor", aggr.stakeholders);
    +				}
    +
    +				attribSince(aggr.since);
    +				if ( aggr.bindable ) {
    +					attrib("bindable", aggr.bindable, false, /* raw = */true);
    +				}
    +				if ( aggr.dnd ) {
    +					curr.dnd = aggr.dnd;
    +				}
    +				tag("description", normalizeWS(aggr.doc), true);
    +				tagWithSince("experimental", aggr.experimental);
    +				tagWithSince("deprecated", aggr.deprecation);
    +				methodList("methods", aggr.methods);
    +				closeTag("aggregation");
    +			}
    +			endCollection("aggregations");
    +		}
    +
    +		if ( metadata.defaultAggregation ) {
    +			tag("defaultAggregation", metadata.defaultAggregation);
    +		}
    +
    +		if ( metadata.associations && Object.keys(metadata.associations).length > 0 ) {
    +			collection("associations");
    +			for ( const n in metadata.associations ) {
    +				const assoc = metadata.associations[n];
    +				tag("association");
    +				attrib("name", assoc.name);
    +				attrib("singularName", assoc.singularName); // TODO omit default?
    +				attrib("type", assoc.type, 'sap.ui.core.Control');
    +				attrib("cardinality", assoc.cardinality, '0..1');
    +				attrib("visibility", assoc.visibility, 'public');
    +				if (assoc.stakeholders) {
    +					stakeholderList("allowedFor", assoc.stakeholders);
    +				}
    +
    +				attribSince(assoc.since);
    +				tag("description", normalizeWS(assoc.doc), true);
    +				tagWithSince("experimental", assoc.experimental);
    +				tagWithSince("deprecated", assoc.deprecation);
    +				methodList("methods", assoc.methods);
    +				closeTag("association");
    +			}
    +			endCollection("associations");
    +		}
    +
    +		if ( metadata.events && Object.keys(metadata.events).length > 0 ) {
    +			collection("events");
    +			for ( const n in metadata.events ) {
    +				const event = metadata.events[n];
    +				tag("event");
    +				attrib("name", event.name);
    +				attrib("visibility", event.visibility, 'public');
    +				if (event.stakeholders) {
    +					stakeholderList("allowedFor", event.stakeholders);
    +				}
    +				if ( event.allowPreventDefault ) {
    +					attrib("allowPreventDefault", event.allowPreventDefault, false, /* raw = */true);
    +				}
    +				if ( event.enableEventBubbling ) {
    +					attrib("enableEventBubbling", event.enableEventBubbling, false, /* raw = */true);
    +				}
    +				attribSince(event.since);
    +				tag("description", normalizeWS(event.doc), true);
    +				tagWithSince("experimental", event.experimental);
    +				tagWithSince("deprecated", event.deprecation);
    +				if ( event.parameters && Object.keys(event.parameters).length > 0 ) {
    +					tag("parameters");
    +					for ( const pn in event.parameters ) {
    +						if ( event.parameters.hasOwnProperty(pn) ) {
    +							const param = event.parameters[pn];
    +							tag(pn);
    +							attrib("name", pn);
    +							attrib("type", param.type);
    +							attribSince(param.since);
    +							tag("description", normalizeWS(param.doc), true);
    +							tagWithSince("experimental", param.experimental);
    +							tagWithSince("deprecated", param.deprecation);
    +							closeTag(pn);
    +						}
    +					}
    +					closeTag("parameters");
    +				}
    +				methodList("methods", event.methods, true);
    +				closeTag("event");
    +			}
    +			endCollection("events");
    +		}
    +
    +		if ( metadata.annotations && Object.keys(metadata.annotations).length > 0 ) {
    +			collection("annotations");
    +			for ( const n in metadata.annotations ) {
    +				const anno = metadata.annotations[n];
    +				tag("annotation");
    +				attrib("name", anno.name);
    +				attrib("namespace", anno.namespace);
    +				if ( anno.target && anno.target.length > 0 ) {
    +					curr.target = anno.target.slice();
    +				}
    +				attrib("annotation", anno.annotation);
    +				attrib("defaultValue", anno.defaultValue);
    +				if ( anno.appliesTo && anno.appliesTo.length > 0 ) {
    +					curr.appliesTo = anno.appliesTo.slice();
    +				}
    +				attribSince(anno.since);
    +				tag("description", normalizeWS(anno.doc), true);
    +				tagWithSince("deprecated", anno.deprecation);
    +				closeTag("annotation");
    +			}
    +			endCollection("annotations");
    +		}
    +
    +		if ( metadata.designtime ) { // don't write falsy values
    +			tag("designtime", metadata.designtime);
    +		}
    +
    +	}
    +
    +	function writeParameterProperties(param, params) {
    +		const prefix = param.name + '.';
    +		const altPrefix = isArrayType(param.type) ? param.name + '[].' : null;
    +
    +		let count = 0;
    +		for ( let i = 0; i < params.length; i++ ) {
    +
    +			let name = params[i].name;
    +			if ( altPrefix && name.lastIndexOf(altPrefix, 0) === 0 ) { // startsWith
    +				name = name.slice(altPrefix.length);
    +			} else if ( name.lastIndexOf(prefix, 0) === 0 ) { // startsWith
    +				if ( altPrefix ) {
    +					warning("Nested @param tag in the context of an array type is used without []-suffix", name);
    +				}
    +				name = name.slice(prefix.length);
    +			} else {
    +				continue;
    +			}
    +
    +			if ( isNestedPropertyName(name) ) {
    +				continue;
    +			}
    +
    +			if ( count === 0 ) {
    +				tag("parameterProperties");
    +			}
    +
    +			count++;
    +
    +			tag(name);
    +			attrib("name", name);
    +			attrib("type", listTypes(params[i].type));
    +			attrib("optional", !!params[i].optional, false, /* raw = */true);
    +			if ( params[i].defaultvalue !== undefined ) {
    +				attrib("defaultValue", params[i].defaultvalue, undefined, /* raw = */true);
    +			}
    +			attribSince(params[i].since);
    +
    +			writeParameterProperties(params[i], params);
    +
    +			tag("description", normalizeWS(params[i].description), true);
    +			tagWithSince("experimental", params[i].experimental);
    +			tagWithSince("deprecated", params[i].deprecated);
    +
    +			closeTag(name);
    +		}
    +
    +		if ( count > 0 ) {
    +			closeTag("parameterProperties");
    +		}
    +	}
    +
    +	function writePropertyProperties(prop, symbol) {
    +		const properties = symbol.properties;
    +		const prefix = prop.name + '.';
    +		const altPrefix = isArrayType(prop.type) ? prop.name + '[].' : null;
    +
    +		let count = 0;
    +		for ( let i = 0; i < properties.length; i++ ) {
    +
    +			let name = properties[i].name;
    +			if ( altPrefix && name.lastIndexOf(altPrefix, 0) === 0 ) { // startsWith
    +				name = name.slice(altPrefix.length);
    +			} else if ( name.lastIndexOf(prefix, 0) === 0 ) { // startsWith
    +				if ( altPrefix ) {
    +					warning("Nested @property tag in the context of an array type is used without []-suffix", name);
    +				}
    +				name = name.slice(prefix.length);
    +			} else {
    +				continue;
    +			}
    +
    +			if ( name.indexOf('.') >= 0 ) {
    +				continue;
    +			}
    +
    +			if ( count === 0 ) {
    +				tag("properties");
    +			}
    +
    +			count++;
    +
    +			tag(name);
    +			attrib("name", name);
    +			attrib("type", listTypes(properties[i].type));
    +			attrib("optional", !!properties[i].optional, false, /* raw = */true);
    +			writePropertyProperties(properties[i], symbol);
    +			tag("description", normalizeWS(properties[i].description), true);
    +			attrib("visibility", visibility(symbol), 'public'); // properties inherit visibility of typedef
    +			if ( stakeholders(symbol) ) {
    +				stakeholderList("allowedFor", stakeholders(symbol));
    +			}
    +
    +			// as JSDoc does not allow tags for other tags, no @deprecated etc. can be supported
    +			// tagWithSince("experimental", properties[i].experimental);
    +			// tagWithSince("deprecated", properties[i].deprecated);
    +
    +			closeTag(name);
    +		}
    +
    +		if ( count > 0 ) {
    +			closeTag("properties");
    +		}
    +	}
    +
    +	function methodSignature(member, suppressReturnValue) {
    +
    +		if ( member.typeParameters && member.typeParameters.length ) {
    +			collection("typeParameters");
    +			for (let j = 0; j < member.typeParameters.length; j++) {
    +				const typeParam = member.typeParameters[j];
    +				tag("typeParameter");
    +				attrib("name", typeParam.name);
    +				if ( typeParam.type ) {
    +					// 'type' is optional for type parameters
    +					attrib("type", listTypes(typeParam.type));
    +				}
    +				if ( typeParam.defaultvalue !== undefined ) {
    +					attrib("default", typeParam.defaultvalue, undefined, /* raw = */true);
    +				}
    +				closeTag("typeParameter");
    +			}
    +			endCollection("typeParameters");
    +		}
    +
    +		if ( !suppressReturnValue ) {
    +			const returns = member.returns && member.returns.length && member.returns[0];
    +			const type = listTypes((returns && returns.type) || member.type);
    +			//if ( type && type !== 'void' ) {
    +			//	attrib("type", type, 'void');
    +			//}
    +			if ( type && type !== 'void' || returns && returns.description ) {
    +				tag("returnValue");
    +				if ( type && type !== 'void' ) {
    +					attrib("type", type);
    +				}
    +				if ( returns && returns.description ) {
    +					attrib("description", normalizeWS(returns.description));
    +				}
    +				closeTag("returnValue");
    +			}
    +		}
    +
    +		const omissibleParams = new Set(member.__ui5 && member.__ui5.omissibleParams);
    +		if ( member.params && member.params.length > 0 ) {
    +			collection("parameters");
    +			for ( let j = 0; j < member.params.length; j++) {
    +				const param = member.params[j];
    +				if ( param.name.indexOf('.') >= 0 ) {
    +					continue;
    +				}
    +				tag("parameter");
    +				attrib("name", param.name);
    +				attrib("type", listTypes(param.type));
    +				attrib("optional", !!param.optional, false, /* raw = */true);
    +				if ( omissibleParams.has(param.name) ) {
    +					if ( !param.optional ) {
    +						throw new Error(`@param ${param.name} is specified in '@ui5-omissible-params' for '${member.name}', but not marked as optional. Only optional params can be omissible.`);
    +					}
    +					attrib("omissible", true, false, /* raw = */true);
    +					omissibleParams.delete(param.name);
    +				}
    +				if ( param.variable ) {
    +					attrib("repeatable", !!param.variable, false, /* raw = */true);
    +				}
    +				if ( param.defaultvalue !== undefined ) {
    +					attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
    +				}
    +				attribSince(param.since);
    +				writeParameterProperties(param, member.params);
    +				tag("description", normalizeWS(param.description), true);
    +				tagWithSince("experimental", param.experimental);
    +				tagWithSince("deprecated", param.deprecated);
    +				closeTag("parameter");
    +			}
    +			endCollection("parameters");
    +		}
    +		if ( omissibleParams.size > 0 ) {
    +			throw new Error(`parameter(s) '${[...omissibleParams].join("' and '")}' specified as '@ui5-omissible-params' for '${member.name}' missing among the actual @params`);
    +		}
    +
    +		exceptions(member);
    +
    +	}
    +
    +	function writeMethod(member, name, canBeOptional) {
    +		name = name || member.name;
    +		const optional = /\?$/.test(name);
    +		if ( optional ) {
    +			name = name.slice(0,-1);
    +		}
    +		tag("method");
    +		attrib("name", name || member.name);
    +		writeModuleInfo(member, symbol);
    +		attrib("visibility", visibility(member), 'public');
    +		if ( stakeholders(member) ) {
    +			stakeholderList("allowedFor", stakeholders(member));
    +		}
    +		if ( member.scope === 'static' ) {
    +			attrib("static", true, false, /* raw = */true);
    +		}
    +		attribSince(member.since);
    +		if ( member.tags && member.tags.some((tag) => tag.title === 'ui5-metamodel') ) {
    +			attrib('ui5-metamodel', true, false, /* raw = */true);
    +		}
    +		if ( canBeOptional && optional ) {
    +			attrib("optional", true, false, /* raw = */true);
    +		}
    +
    +		methodSignature(member);
    +
    +		tag("description", normalizeWS(member.description), true);
    +		tagWithSince("experimental", member.experimental);
    +		tagWithSince("deprecated", member.deprecated);
    +		examples(member);
    +		referencesList(member);
    +		if ( member.__ui5.tsSkip ) {
    +			attrib("tsSkip", true, false, /* raw = */true);
    +		}
    +		//secTags(member);
    +		closeTag("method");
    +
    +	}
    +
    +	/*
    +	let rSplitSecTag = /^\s*\{([^\}]*)\}/;
    +
    +	function secTags($) {
    +		if ( true ) {
    +			return;
    +		}
    +		let aTags = $.tags;
    +		if ( !aTags ) {
    +			return;
    +		}
    +		for (let iTag = 0; iTag < A_SECURITY_TAGS.length; iTag++  ) {
    +			let oTagDef = A_SECURITY_TAGS[iTag];
    +			for (let j = 0; j < aTags.length; j++ ) {
    +				if ( aTags[j].title.toLowerCase() === oTagDef.name.toLowerCase() ) {
    +					tag(oTagDef.name);
    +					let m = rSplitSecTag.exec(aTags[j].text);
    +					if ( m && m[1].trim() ) {
    +						let aParams = m[1].trim().split(/\s*\|\s* /); <-- remember to remove the space!
    +						for (let iParam = 0; iParam < aParams.length; iParam++ ) {
    +							tag(oTagDef.params[iParam], aParams[iParam]);
    +						}
    +					}
    +					let sDesc = aTags[j].description;
    +					tag("description", sDesc, true);
    +					closeTag(oTagDef.name);
    +				}
    +			}
    +		}
    +	}
    +	*/
    +
    +	const kind = ((symbol.kind === 'member' || symbol.kind === 'constant') && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
    +
    +	tag(kind);
    +
    +	attrib("name", symbol.longname);
    +	attrib("basename", basename(symbol.name));
    +	if ( symbol.__ui5.resource ) {
    +		attrib("resource", symbol.__ui5.resource);
    +	}
    +	if ( symbol.__ui5.module ) {
    +		attrib("module", symbol.__ui5.module);
    +		attrib("export", symbol.__ui5.globalOnly ? GLOBAL_ONLY : symbol.__ui5.export, '', true);
    +	}
    +	if ( /* TODO (kind === 'class') && */ symbol.virtual ) {
    +		// Note reg. the TODO: only one unexpected occurrence found in DragSession (DragAndDrop.js)
    +		attrib("abstract", true, false, /* raw = */true);
    +	}
    +	if ( /* TODO (kind === 'class' || kind === 'interface') && */ symbol.final_ ) {
    +		// Note reg. the TODO: enums are marked as final & namespace, they would loose the final with the addtl. check.
    +		attrib("final", true, false, /* raw = */true);
    +	}
    +	if ( symbol.scope === 'static' ) {
    +		attrib("static", true, false, /* raw = */true);
    +	}
    +	attrib("visibility", visibility(symbol), 'public');
    +	if ( stakeholders(symbol) ) {
    +		stakeholderList("allowedFor", stakeholders(symbol));
    +	}
    +	attribSince(symbol.since);
    +	/* TODO if ( kind === 'class' || kind === 'interface' ) { */
    +		// Note reg. the TODO: some objects document that they extend other objects. JSDoc seems to support this use case
    +		// (properties show up as 'borrowed') and the borrowed entities also show up in the SDK (but not the 'extends' relationship itself)
    +		if ( symbol.augments && symbol.augments.length ) {
    +			tag("extends", symbol.augments.sort().join(",")); // TODO what about multiple inheritance?
    +		}
    +		interfaceList("implements", symbol.implements);
    +	/* } */
    +	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === 'class' ? '' : symbol.description)), true);
    +	if ( kind !== 'class' ) {
    +		examples(symbol); // for a class, examples are added to the constructor
    +		referencesList(symbol); // for a class, references are added to the constructor
    +	}
    +	tagWithSince("experimental", symbol.experimental);
    +	tagWithSince("deprecated", symbol.deprecated);
    +	if ( symbol.tags && symbol.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
    +		attrib('ui5-metamodel', true, false, /* raw = */true);
    +	}
    +
    +	let skipMembers = false;
    +	let standardEnum = false;
    +
    +	if ( kind === 'class' ) {
    +
    +		if ( symbol.__ui5.stereotype || (symbol.__ui5.metadata && symbol.__ui5.metadata.metadataClass) || hasSettings(symbol) ) {
    +
    +			tag("ui5-metadata");
    +
    +			if ( symbol.__ui5.stereotype ) {
    +				attrib("stereotype", symbol.__ui5.stereotype);
    +			}
    +			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.metadataClass ) {
    +				attrib("metadataClass", symbol.__ui5.metadata.metadataClass);
    +			}
    +
    +			writeMetadata(symbol);
    +
    +			closeTag("ui5-metadata");
    +		}
    +
    +
    +		// if @hideconstructor tag is present we omit the whole constructor
    +		if ( !symbol.hideconstructor ) {
    +
    +			tag("constructor");
    +			attrib("visibility", visibility(symbol));
    +			if ( stakeholders(symbol) ) {
    +				stakeholderList("allowedFor", stakeholders(symbol));
    +			}
    +			methodSignature(symbol, /* suppressReturnValue = */ true);
    +
    +			tag("description", normalizeWS(symbol.description), true);
    +			// tagWithSince("experimental", symbol.experimental); // TODO repeat from class?
    +			// tagWithSince("deprecated", symbol.deprecated); // TODO repeat from class?
    +			examples(symbol); // TODO here or for class?
    +			referencesList(symbol); // TODO here or for class?
    +			// secTags(symbol); // TODO repeat from class?
    +			closeTag("constructor");
    +
    +		} else {
    +
    +			// even though the constructor is omitted here, the "hide" information is needed because in TypeScript it even exists when omitted
    +			attrib("hideconstructor", true, /* default */ false, /* raw */ true); // as boolean
    +
    +		}
    +
    +	} else if ( kind === 'namespace' ) {
    +		if ( symbol.__ui5.stereotype || symbol.__ui5.metadata ) {
    +			tag("ui5-metadata");
    +
    +			if ( symbol.__ui5.stereotype ) {
    +				attrib("stereotype", symbol.__ui5.stereotype);
    +			}
    +
    +			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.basetype ) {
    +				attrib("basetype", symbol.__ui5.metadata.basetype);
    +			}
    +
    +			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.pattern ) {
    +				attrib("pattern", symbol.__ui5.metadata.pattern);
    +			}
    +
    +			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.range ) {
    +				attrib("range", symbol.__ui5.metadata.range, null, /* raw = */ true);
    +			}
    +
    +			closeTag("ui5-metadata");
    +		}
    +	} else if ( kind === 'typedef' ) {
    +		// typedefs have their own property structure
    +		skipMembers = true;
    +		if ( symbol.params || symbol.returns || symbol.exceptions ) {
    +			methodSignature(symbol);
    +		} else if ( symbol.properties && symbol.properties.length > 0 ) {
    +			if ( symbol.type ) { // "type" of a typedef defines its inheritance base
    +				const type = listTypes(symbol.type);
    +				if ( type.toLowerCase() !== "object" ) {
    +					attrib("extends", type);
    +				}
    +			}
    +			collection("properties");
    +			symbol.properties.forEach((prop) => {
    +				if ( prop.name.indexOf('.') >= 0 ) {
    +					return;
    +				}
    +				tag("property");
    +				attrib("name", prop.name);
    +				attrib("type", listTypes(prop.type));
    +				attrib("optional", !!prop.optional, false, /* raw = */true);
    +				writePropertyProperties(prop, symbol);
    +				attrib("visibility", visibility(symbol), 'public'); // properties inherit visibility of typedef
    +				if ( stakeholders(symbol) ) {
    +					stakeholderList("allowedFor", stakeholders(symbol));
    +				}
    +				tag("description", normalizeWS(prop.description), true);
    +				closeTag("property");
    +			});
    +			endCollection("properties");
    +		} else if ( symbol.type ) {
    +			// a type alias
    +			attrib("type", listTypes(symbol.type));
    +		}
    +	} else if ( kind === 'enum' ) {
    +		if ( symbol.__ui5.stereotype ) {
    +			tag("ui5-metadata");
    +			attrib("stereotype", symbol.__ui5.stereotype);
    +			standardEnum = symbol.__ui5.stereotype === 'enum';
    +			closeTag("ui5-metadata");
    +		}
    +	} else if ( kind === 'function' ) {
    +		methodSignature(symbol);
    +	}
    +
    +	if ( !skipMembers ) {
    +		const ownProperties = getMembersOfKind(symbol, "property").sort(sortByAlias);
    +		if ( ownProperties.length > 0 ) {
    +			collection("properties");
    +			for ( let i = 0; i < ownProperties.length; i++ ) {
    +				const member = ownProperties[i];
    +				tag("property");
    +				attrib("name", member.name);
    +				writeModuleInfo(member, symbol);
    +				if ( kind === 'enum' && !standardEnum && member.__ui5.value !== undefined ) {
    +					attrib("value", member.__ui5.value, undefined, /* raw = */true);
    +				}
    +				attrib("visibility", visibility(member), 'public');
    +				if ( stakeholders(member) ) {
    +					stakeholderList("allowedFor", stakeholders(member));
    +				}
    +				if ( member.scope === 'static' ) {
    +					attrib("static", true, false, /* raw = */true);
    +				}
    +				attribSince(member.since);
    +				attrib("type", listTypes(member.type));
    +				tag("description", normalizeWS(member.description), true);
    +				tagWithSince("experimental", member.experimental);
    +				tagWithSince("deprecated", member.deprecated);
    +				examples(member);
    +				referencesList(member);
    +				if ( member.__ui5.tsSkip ) {
    +					attrib("tsSkip", true, false, /* raw = */true);
    +				}
    +				closeTag("property");
    +			}
    +			endCollection("properties");
    +		}
    +
    +		const ownEvents = getMembersOfKind(symbol, 'event').sort(sortByAlias);
    +		if ( ownEvents.length > 0 ) {
    +			collection("events");
    +			for (let i = 0; i < ownEvents.length; i++ ) {
    +				const member = ownEvents[i];
    +				tag("event");
    +				attrib("name", member.name);
    +				writeModuleInfo(member, symbol);
    +				attrib("visibility", visibility(member), 'public');
    +				if ( stakeholders(member) ) {
    +					stakeholderList("allowedFor", stakeholders(member));
    +				}
    +				if ( member.scope === 'static' ) {
    +					attrib("static", true, false, /* raw = */true);
    +				}
    +				attribSince(member.since);
    +
    +				if ( member.params && member.params.length > 0 ) {
    +					collection("parameters");
    +					for (let j = 0; j < member.params.length; j++) {
    +						const param = member.params[j];
    +						if ( param.name.indexOf('.') >= 0 ) {
    +							continue;
    +						}
    +
    +						tag("parameter");
    +						attrib("name", param.name);
    +						attrib("type", listTypes(param.type));
    +						if ( param.since ) {
    +							attrib("since", extractVersion(param.since));
    +						}
    +						writeParameterProperties(param, member.params);
    +						tag("description", normalizeWS(param.description), true);
    +						tagWithSince("experimental", param.experimental);
    +						tagWithSince("deprecated", param.deprecated);
    +						closeTag("parameter");
    +					}
    +					endCollection("parameters");
    +				}
    +				tag("description", normalizeWS(member.description), true);
    +				tagWithSince("deprecated", member.deprecated);
    +				tagWithSince("experimental", member.experimental);
    +				examples(member);
    +				referencesList(member);
    +				//secTags(member);
    +				closeTag("event");
    +			}
    +			endCollection("events");
    +		}
    +
    +		const ownMethods = getMembersOfKind(symbol, 'method').sort(sortByAlias);
    +		// xmlmacro stereotype does not allow methods
    +		if ( symbol.__ui5.stereotype !== 'xmlmacro' && ownMethods.length > 0 ) {
    +			collection("methods");
    +			ownMethods.forEach(function(member) {
    +				writeMethod(member, undefined, symbol.kind === 'interface' || symbol.kind === 'class');
    +				if ( member.__ui5.members ) {
    +					// HACK: export nested static functions as siblings of the current function
    +					// A correct representation has to be discussed with the SDK / WebIDE
    +					member.__ui5.members.forEach(function($) {
    +						if ( $.kind === 'function' && $.scope === 'static'
    +							 && conf.filter($) && !$.inherited ) {
    +							future(`exporting nested function '${member.name}.${$.name}'`);
    +							writeMethod($, member.name + "." + $.name);
    +						}
    +					});
    +				}
    +			});
    +			endCollection("methods");
    +		}
    +
    +	//	if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
    +	//		collection("children", "kind");
    +	//		symbol.__ui5.children.forEach(writeSymbol);
    +	//		endCollection("children");
    +	//	}
    +	}
    +
    +	closeTag(kind);
    +
    +	return obj[0];
    +}
    +
    +function addExportInfo(symbols) {
    +	const modules = {};
    +
    +	// collect modules and the symbols that refer to them
    +	for ( let i = 0; i < symbols.length; i++) {
    +		const symbol = symbols[i];
    +		if ( symbol.module ) {
    +			modules[symbol.module] = modules[symbol.module] || [];
    +			modules[symbol.module].push({
    +				name: symbol.name,
    +				symbol: symbol
    +			});
    +		}
    +		if ( symbol.properties ) {
    +			for ( let j = 0; j < symbol.properties.length; j++ ) {
    +				if ( symbol.properties[j].static && symbol.properties[j].module ) {
    +					modules[symbol.properties[j].module] = modules[symbol.properties[j].module] || [];
    +					modules[symbol.properties[j].module].push({
    +						name: symbol.name + "." + symbol.properties[j].name,
    +						symbol: symbol.properties[j]
    +					});
    +				}
    +			}
    +		}
    +		if ( symbol.methods ) {
    +			for ( let j = 0; j < symbol.methods.length; j++ ) {
    +				if ( symbol.methods[j].static && symbol.methods[j].module ) {
    +					modules[symbol.methods[j].module] = modules[symbol.methods[j].module] || [];
    +					modules[symbol.methods[j].module].push({
    +						name: symbol.name + "." + symbol.methods[j].name,
    +						symbol: symbol.methods[j]
    +					});
    +				}
    +			}
    +		}
    +	}
    +
    +	function guessExports(moduleName, symbols) {
    +
    +		// a non-stringifiable special value for unresolved exports
    +		const UNRESOLVED = function() {};
    +
    +		symbols = symbols.sort((a,b) => {
    +			if ( a.name === b.name ) {
    +				return 0;
    +			}
    +			return a.name < b.name ? -1 : 1;
    +		});
    +
    +		// info(`resolving exports of '${n}': ${symbols.map((symbol) => symbol.name)`);
    +		const moduleNamePath = "module:" + moduleName;
    +		let defaultExport;
    +		if ( /^jquery\.sap\./.test(moduleName) ) {
    +			// the jquery.sap.* modules all export 'jQuery'.
    +			// any API from those modules is reachable via 'jQuery.*'
    +			defaultExport = 'jQuery';
    +		} else {
    +			// library.js modules export the library namespace; for all other modules, the assumed default export
    +			// is identical to the name of the module (converted to a 'dot' name)
    +			defaultExport = moduleName.replace(/\/library$/, "").replace(/\//g, ".");
    +		}
    +
    +		// Pass 1: determine exports where possible, mark others with UNRESOLVED
    +		symbols.forEach(function(symbol) {
    +			if ( symbol.symbol.export !== undefined && symbol.symbol.export !== GLOBAL_ONLY ) {
    +				// export name was already determined during parsing
    +				debug(`    ${symbol.symbol.export || "(default)"}: ${symbol.name} (derived from source)`);
    +				return;
    +			}
    +			// debug(`check ${symbol.name} against ${defaultExport} and ${moduleNamePath}`);
    +			if ( symbol.symbol.kind === "typedef" || symbol.symbol.kind === "interface" || symbol.symbol.export === GLOBAL_ONLY ) {
    +				// type definitions, interfaces and symbols marked with @ui5-global-only have no representation on module level
    +				symbol.symbol.export = undefined;
    +			} else if ( symbol.name === moduleNamePath ) {
    +				// symbol name is the same as the module namepath -> symbol is the default export
    +				symbol.symbol.export = "";
    +			} else if ( symbol.name.lastIndexOf(moduleNamePath + ".", 0) === 0 ) {
    +				// symbol name starts with the module namepath and a dot -> symbol is a named export (static)
    +				symbol.symbol.export = symbol.name.slice(moduleNamePath.length + 1);
    +			} else if ( symbol.name === defaultExport ) {
    +				// default export equals the symbol name
    +				symbol.symbol.export = "";
    +				//debug(`    (default):${defaultExport}`);
    +			} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
    +				// default export is a prefix of the symbol name
    +				symbol.symbol.export = symbol.name.slice(defaultExport.length + 1);
    +				//debug(`    ${symbol.name.slice(defaultExport.length + 1)}: ${symbol.name}`);
    +			} else {
    +				// default export is not a prefix of the symbol name -> no way to access it in AMD
    +				symbol.symbol.export = UNRESOLVED;
    +			}
    +		});
    +
    +		// Pass 2: check whether unresolved exports are critical or not
    +		symbols.forEach(function(symbol) {
    +			if ( symbol.symbol.export === UNRESOLVED ) {
    +				symbol.symbol.export = undefined;
    +				// an export is expected when a symbol is not a namespace or
    +				// when a namespace has children from the same module that are also lacking an export
    +				if ( symbol.symbol.kind !== "namespace"
    +					 || (symbol.symbol.properties && symbol.symbol.properties.some(function(prop) {
    +						 return prop.module === symbol.symbol.module && prop.export == null;
    +					 }) )
    +					 || (symbol.symbol.methods && symbol.symbol.methods.some(function(method) {
    +						 return method.module === symbol.symbol.module && method.export == null;
    +					 }) ) ) {
    +					future(`could not identify export name of '${symbol.name}', contained in module '${moduleName}'`);
    +				} else {
    +					debug(`could not identify export name of ${symbol.symbol.kind} '${symbol.name}', contained in module '${moduleName}'`);
    +				}
    +			}
    +			if ( symbol.symbol.kind === "namespace" && symbol.symbol.export === undefined ) {
    +				// if no export could be identified for a namespace, don't annotate the namespace with a module
    +				symbol.symbol.resource =
    +				symbol.symbol.module = undefined;
    +			}
    +		});
    +
    +	}
    +
    +	for ( const n in modules ) {
    +		guessExports(n, modules[n]);
    +	}
    +}
    +
    +function postProcessAPIJSON(api) {
    +
    +	const symbols = api.symbols;
    +
    +	addExportInfo(symbols);
    +
    +	function findSymbol(name) {
    +		if ( name == null || name === '' ) {
    +			return null;
    +		}
    +		return symbols.find((candidate) => candidate.name === name) || externalSymbols[name];
    +	}
    +
    +	function findMetadataClass(symbol) {
    +		while ( symbol ) {
    +			if ( symbol["ui5-metadata"] && symbol["ui5-metadata"].metadataClass ) {
    +				const metadataSymbol = findSymbol(symbol["ui5-metadata"].metadataClass);
    +				if ( metadataSymbol != null && metadataSymbol.visibility === "public" ) {
    +					return symbol["ui5-metadata"].metadataClass;
    +				}
    +			}
    +			symbol = findSymbol(symbol.extends);
    +		}
    +		return undefined;
    +	}
    +
    +	// See sap/ui/base/ManagedObjectMetadata
    +	const rLibName = /([a-z][^.]*(?:\.[a-z][^.]*)*)\./;
    +	function defaultLibName(sName) {
    +		var m = rLibName.exec(sName);
    +		return (m && m[1]) || "";
    +	}
    +
    +	// create a 2-level map of symbol names, first keyed by the exporting module and then by export name
    +	const modules = Object.create(null);
    +	for (const symbol of Object.values(externalSymbols)) {
    +		if ( symbol.module && symbol.export !== undefined ) {
    +			modules[symbol.module] ??= Object.create(null);
    +			modules[symbol.module][symbol.export] = symbol.name;
    +		}
    +	}
    +	for (const symbol of symbols) {
    +		if ( symbol.module && symbol.export !== undefined ) {
    +			modules[symbol.module] ??= Object.create(null);
    +			modules[symbol.module][symbol.export] = symbol.name;
    +		}
    +	}
    +
    +	function findSymbolByModule(module, exportName) {
    +		if (module === "unknown") {
    +			return "unknown";
    +		}
    +		return modules[module]?.[exportName] ?? "unknown";
    +	}
    +
    +	symbols.forEach((symbol) => {
    +		// add note for enums which are not exposed by their name
    +		if (symbol.kind === "enum" && symbol.export) {
    +			symbol.description += `\n\nThis enum is part of the '${symbol.module}' module export and must be accessed by the property '${symbol.export}'.`;
    +		}
    +		if ( !symbol["ui5-metadata"] ) {
    +			return;
    +		}
    +		if ( Array.isArray(symbol.methods) ) {
    +			symbol.methods.forEach((method) => {
    +				if ( method.name === "getMetadata" && method.returnValue ) {
    +					const metadataClass = findMetadataClass(symbol);
    +					if ( metadataClass && metadataClass !== method.returnValue.type ) {
    +						method.returnValue.type = metadataClass;
    +						debug(`  return type of '${symbol.name}${method.static ? "." : "#"}getMetadata' changed to '${metadataClass}'`);
    +					}
    +				}
    +			});
    +		}
    +		const libraryName = symbol["ui5-metadata"].library;
    +		if ( libraryName ) {
    +			// Removing "library" property from api.json as it contains redundant information and is not required by consumers
    +			// It is only required for the consistency checks below
    +			delete symbol["ui5-metadata"].library;
    +
    +			if ( libraryName !== api.library ) {
    +				error(`specified library '${libraryName}' for class '${symbol.name}' doesn't match containing library '${api.library}'`);
    +			}
    +		} else if ( symbol["ui5-metadata"].stereotype === "element" || symbol["ui5-metadata"].stereotype === "control" ) {
    +			// The library property is only relevant for elements and controls.
    +			// The derived library name must not be checked for other classes such as types.
    +			const derivedLibraryName = defaultLibName(symbol.name);
    +			if (derivedLibraryName !== api.library) {
    +				future(`derived library '${derivedLibraryName}' for ${symbol["ui5-metadata"].stereotype} '${symbol.name}' ` +
    +					`doesn't match containing library '${api.library}'. Library must be explicitly defined in class metadata!`);
    +			}
    +		}
    +		if (Array.isArray(symbol["ui5-metadata"].properties)) {
    +			for (const prop of symbol["ui5-metadata"].properties) {
    +				let moduleType;
    +				// cut off array brackets for symbol lookup
    +				const arrType = isArrayType(prop.type);
    +				let lookupType = prop.type;
    +				if (arrType) {
    +					lookupType = lookupType.replace("[]", "");
    +				}
    +				if (prop.dataType === undefined
    +					&& findSymbol(lookupType) == null
    +					&& !lookupType?.startsWith("module:")
    +					&& findSymbol(moduleType = `module:${lookupType.replace(/\./g, "/")}`) != null) {
    +					// note: the dataType must be the original, including array brackets if present
    +					prop.dataType = prop.type;
    +					// the moduleType also needs to include the array brackets if present in the original dataType
    +					prop.type = moduleType + (arrType ? "[]" : "");
    +					for (const methodName of prop.methods) {
    +						const method = symbol.methods?.find((m) => m.name === methodName);
    +						if (methodName.startsWith("get")
    +							&& method?.returnValue?.type === prop.dataType) {
    +							method.returnValue.type = prop.type;
    +						} else if (methodName.startsWith("set")
    +							&& method?.parameters?.[0]?.type === prop.dataType) {
    +							method.parameters[0].type = prop.type;
    +						}
    +					}
    +					info(`${symbol.name}: adapted type of ${prop.name} and its accessors from '${prop.dataType}' to '${prop.type}'`);
    +				}
    +			}
    +		}
    +		/*
    +		 * Up to here, the defaultClass only contains a module name. Replace it with the corresponding API (type) name.
    +		 */
    +		if (Array.isArray(symbol["ui5-metadata"].aggregations)) {
    +			for (const aggr of symbol["ui5-metadata"].aggregations) {
    +				if (aggr.defaultClass != null) {
    +					aggr.defaultClass = findSymbolByModule(aggr.defaultClass, "");
    +				}
    +			}
    +		}
    +	});
    +
    +}
    +
    +const builtinTypes = {
    +	"void":true,
    +	any:true,
    +	"boolean":true,
    +	"int": true,
    +	"float":true,
    +	array:true,
    +	"function":true,
    +	string:true,
    +	object:true,
    +	"*": true,
    +	number:true,
    +	"null":true,
    +	undefined:true,
    +	"this":true,
    +
    +	// builtin objects
    +	Array:true,
    +	ArrayBuffer:true,
    +	Boolean:true,
    +	Date:true,
    +	Error:true,
    +	Map:true,
    +	Number:true,
    +	String:true,
    +	Object:true,
    +	Promise:true,
    +	RegExp:true,
    +	Set:true,
    +	SyntaxError:true,
    +	TypeError:true,
    +	Uint8Array:true,
    +	WeakMap: true,
    +
    +	// Web APIs
    +	AbortSignal:true,
    +	Attr:true,
    +	Blob:true,
    +	DataTransfer:true,
    +	DragEvent:true,
    +	Document:true,
    +	DOMException:true,
    +	DOMRect:true,
    +	Element:true,
    +	Event:true,
    +	File:true,
    +	FileList:true,
    +	FormData:true,
    +	Headers:true,
    +	HTMLDocument:true,
    +	HTMLElement:true,
    +	Node:true,
    +	PerformanceResourceTiming:true,
    +	Request:true,
    +	Response:true,
    +	Storage:true,
    +	Touch:true,
    +	TouchList:true,
    +	Window:true,
    +	XMLDocument:true
    +
    +};
    +
    +const typeNormalizer = (function() {
    +	class TypeNormalizer extends TypeStringBuilder {
    +		simpleType(type) {
    +			if ( type === 'map' ) {
    +				return this.object(
    +					this.simpleType('string'),
    +					this.simpleType('any')
    +				);
    +			}
    +			if ( type === '*' ) {
    +				type = 'any';
    +			}
    +			return super.simpleType(type);
    +		}
    +	}
    +
    +	return new TypeNormalizer();
    +}());
    +
    +/**
    + * Wrong, but commonly used type names and their correct replacement.
    + */
    +const erroneousTypes = {
    +	"integer": "int",
    +	"bool": "boolean",
    +	"double": "float",
    +	"long": "int",
    +	"int8": "int",
    +	"int16": "int",
    +	"int32": "int",
    +	"int64": "int",
    +	"uint8": "int",
    +	"uint16": "int",
    +	"uint32": "int",
    +	"uint64": "int"
    +};
    +
    +function validateAPIJSON(api) {
    +
    +	// create map of defined symbols (built-in types, dependency libraries, current library)
    +	const defined = Object.assign(Object.create(null), builtinTypes, externalSymbols);
    +	if ( api.symbols ) {
    +		api.symbols.forEach((symbol) => {
    +			defined[symbol.name] = symbol;
    +		});
    +	}
    +
    +	const naming = Object.create(null);
    +	const missing = Object.create(null);
    +
    +	const rValidNames = /^[$A-Z_a-z][$0-9A-Z_a-z]*$/i;
    +	const rValidModuleNames = /^[$A-Z_a-z][$\-\.0-9A-Z_a-z]*$/i;
    +
    +	let currentTypeParameters = Object.create(null);
    +
    +	function checkName(name, hint) {
    +		if ( !rValidNames.test(name) ) {
    +			naming[name] = naming[name] || [];
    +			naming[name].push(hint);
    +		}
    +	}
    +
    +	/**
    +	 * Checks the name of a property in an object-like structure.
    +	 * As in JavaScript, the rules for such names are less strict than
    +	 * for classes, interfaces or function declarations.
    +	 */
    +	function checkPropertyName(name, hint) {
    +		const match = rStartsWithValidPropertyName.exec(name);
    +		if ( match == null || match[0].length !== name.length ) {
    +			naming[name] ??= [];
    +			naming[name].push(hint);
    +		}
    +	}
    +
    +	function checkModuleName(name, hint) {
    +		if ( !rValidModuleNames.test(name) ) {
    +			naming[name] = naming[name] || [];
    +			naming[name].push(hint);
    +		}
    +	}
    +
    +	function checkCompoundName(name, hint) {
    +
    +		if ( name.startsWith("module:") ) {
    +			const segments = name.slice("module:".length).split("/");
    +
    +			// split last segment into a module name part and a symbol name part
    +			const p = segments[segments.length - 1].search(/[.~#]/);
    +			if ( p >= 0 ) {
    +				name = segments[segments.length - 1].slice(p + 1);
    +				segments[segments.length - 1] = segments[segments.length - 1].slice(0, p);
    +			}
    +
    +			// check all module name parts
    +			segments.forEach((segment) => checkModuleName(segment, `path segment of ${hint}`));
    +
    +			if ( p < 0 ) {
    +				// module name only, no export name to check
    +				return;
    +			}
    +		}
    +
    +		name.split(/[.~#]/).forEach((segment) => checkName(segment, `name segment of ${hint}`));
    +	}
    +
    +	function reportError(type, usage) {
    +		missing[type] = missing[type] || [];
    +		missing[type].push(usage);
    +	}
    +
    +	function checkSimpleType(typeName, hint) {
    +		if (currentTypeParameters[typeName]) {
    +			return;
    +		}
    +		if ( !defined[typeName] ) {
    +			reportError(typeName, hint);
    +		}
    +	}
    +
    +	function checkType(type, hint) {
    +
    +		function _check(type) {
    +			if ( type == null ) {
    +				return;
    +			}
    +			switch (type.type) {
    +			case 'simpleType':
    +				checkSimpleType(type.name, hint);
    +				break;
    +			case 'array':
    +				_check(type.component);
    +				break;
    +			case 'object':
    +				_check(type.key);
    +				_check(type.value);
    +				break;
    +			case 'set':
    +				_check(type.element);
    +				break;
    +			case 'promise':
    +				_check(type.fulfill);
    +				break;
    +			case 'function':
    +				type.params.forEach(_check);
    +				_check(type.return);
    +				_check(type.this);
    +				_check(type.constructor);
    +				break;
    +			case 'structure':
    +				Object.keys(type.fields).forEach((key) => _check(type.fields[key]));
    +				break;
    +			case 'union':
    +				type.types.forEach(_check);
    +				break;
    +			case 'typeApplication':
    +				_check(type.baseType);
    +				type.templateTypes.forEach(_check);
    +				// TODO check number of templateTypes against declaration of baseType
    +				// requires JSDoc support of @template tag, which is currently missing
    +				break;
    +			default:
    +				break;
    +			}
    +		}
    +		try {
    +			// debug("normalize", type);
    +			type.type = typeParser.parse(type.type, typeNormalizer).str;
    +			// debug("check", type);
    +			const ast = typeParser.parse(type.type);
    +			_check(ast);
    +		} catch (e) {
    +			future(e);
    +			reportError(type.type, `failed to parse type of ${hint}`);
    +		}
    +	}
    +
    +	function checkTypeParameter(typeParam, hint) {
    +		checkType(typeParam, `type constraint of ${hint}`);
    +		if ( typeParam.default ) {
    +			checkType({type: typeParam.default}, `default of ${hint}`);
    +		}
    +	}
    +
    +	function checkParam(param, prefix, hint) {
    +		if (prefix) {
    +			// nested parameter properties follow the naming conventions for object properties
    +			checkPropertyName(param.name, `name of param ${prefix}${param.name} of ${hint}`);
    +		} else {
    +			checkName(param.name, `name of param ${prefix}${param.name} of ${hint}`);
    +		}
    +		checkType(param, `param ${prefix}${param.name} of ${hint}`);
    +		if ( param.parameterProperties ) {
    +			Object.keys(param.parameterProperties).forEach((sub) =>
    +				checkParam(param.parameterProperties[sub], prefix + param.name + ".", hint));
    +		}
    +	}
    +
    +	function checkMethodSignature(method, hint, isConstructor = false) {
    +		const prevTypeParams = currentTypeParameters;
    +		if ( !isConstructor && method.typeParameters ) {
    +			// Note: type parameters of the constructor are handled on class level
    +			currentTypeParameters = Object.create(currentTypeParameters);
    +			for (const typeParam of method.typeParameters) {
    +				currentTypeParameters[typeParam.name] = typeParam;
    +			}
    +			// check type parameters in a 2nd loop so that they can depend on each other
    +			for (const typeParam of method.typeParameters) {
    +				checkTypeParameter(typeParam, `type parameter ${typeParam.name} of ${hint}`);
    +			}
    +		}
    +		if ( method.returnValue ) {
    +			checkType(method.returnValue, `return value of ${hint}`);
    +		}
    +		if ( method.parameters ) {
    +			method.parameters.forEach((param) => checkParam(param, '', hint));
    +		}
    +		if ( method.throws ) {
    +			method.throws.forEach((ex) => checkType(ex, `exception of ${hint}`));
    +		}
    +		// restore type parameters of outer scope
    +		currentTypeParameters = prevTypeParams;
    +	}
    +
    +	function checkClassAgainstInterface(symbol, oIntfAPI) {
    +		if ( oIntfAPI.methods ) {
    +			oIntfAPI.methods.forEach((intfMethod) => {
    +				// search for method implementation
    +				const implMethod = symbol.methods && symbol.methods.find(
    +					(candidateMethod) => candidateMethod.name === intfMethod.name && !candidateMethod.static);
    +
    +				if ( !implMethod ) {
    +					if ( !intfMethod.optional ) {
    +						reportError(oIntfAPI.name, `implementation of ${intfMethod.name} missing in ${symbol.name}`);
    +					}
    +				} else {
    +					if ( intfMethod.parameters ) {
    +						intfMethod.parameters.forEach((intfParam, idx) => {
    +							const implParam = implMethod.parameters && implMethod.parameters[idx];
    +							if ( !implParam ) {
    +								if ( !intfParam.optional ) {
    +									reportError(oIntfAPI.name, `parameter ${intfParam.name} missing in implementation of ${symbol.name}#${intfMethod.name}`);
    +								}
    +							} else if ( implParam.type !== intfParam.type ) {
    +								reportError(oIntfAPI.name, `type of parameter ${intfParam.name} of interface method differs from type in implementation ${symbol.name}#${intfMethod.name}`);
    +							}
    +							// TODO check nested properties
    +						});
    +					}
    +					if ( intfMethod.returnValue != null && implMethod.returnValue == null ) {
    +						reportError(oIntfAPI.name, `return value of interface method missing in implementation ${symbol.name}#${intfMethod}`);
    +					} else if ( intfMethod.returnValue == null && implMethod.returnValue != null ) {
    +						reportError(oIntfAPI.name, `while interface method is void, implementation ${symbol.name}#${intfMethod.name} returns a value`);
    +					} else if ( intfMethod.returnValue != null && implMethod.returnValue != null ) {
    +						if ( intfMethod.returnValue.type !== implMethod.returnValue.type ) {
    +							reportError(oIntfAPI.name, `return type of interface method differs from return type of implementation ${symbol.name}#${intfMethod.name}`);
    +						}
    +					}
    +				}
    +			});
    +		}
    +	}
    +
    +	function checkEnum(symbol) {
    +		if ( symbol["ui5-metamodel"]
    +			 && !(symbol["ui5-metadata"] && symbol["ui5-metadata"].stereotype === "enum")
    +			 && Array.isArray(symbol.properties) && symbol.properties.length > 0 ) {
    +			reportError(symbol.name, "enum is metamodel relevant but keys and values differ");
    +		}
    +		checkCompoundName(symbol.name, `name of ${symbol.name}`);
    +		if ( symbol.properties ) {
    +			symbol.properties.forEach((prop) => {
    +				checkName(prop.name, `name of ${symbol.name}.${prop.name}`);
    +				if ( prop.type ) {
    +					checkType(prop, `type of ${symbol.name}.${prop.name}`);
    +				}
    +			});
    +		}
    +	}
    +
    +	function checkClass(symbol) {
    +		const prevTypeParams = currentTypeParameters;
    +		if ( Object.hasOwn(symbol, "constructor") && symbol.constructor.typeParameters ) {
    +			currentTypeParameters = Object.create(currentTypeParameters);
    +			for (const typeParam of symbol.constructor.typeParameters) {
    +				currentTypeParameters[typeParam.name] = typeParam;
    +			}
    +			// Check type parameters in a 2nd loop so that they can depend on each other.
    +			// As of TypeScript 5.9.2, `class Foo {}´ is valid
    +			for (const typeParam of symbol.constructor.typeParameters) {
    +				checkTypeParameter(typeParam, `type parameter ${typeParam.name} of ${symbol.name}`);
    +			}
    +		}
    +
    +		checkCompoundName(symbol.name, `name of ${symbol.name}`);
    +		if ( symbol.extends ) {
    +			checkSimpleType(symbol.extends, `base class of ${symbol.name}`);
    +		}
    +		if ( symbol.implements ) {
    +			symbol.implements.forEach((intf) => {
    +				checkSimpleType(intf, `interface of ${symbol.name}`);
    +				const oIntfAPI = defined[intf];
    +				if ( oIntfAPI ) {
    +					checkClassAgainstInterface(symbol, oIntfAPI);
    +				}
    +			});
    +		}
    +		if ( Object.hasOwn(symbol, "constructor") ) {
    +			checkMethodSignature(symbol.constructor, symbol.name + ".constructor", /* isConstructor */ true);
    +		}
    +		if ( symbol.properties ) {
    +			symbol.properties.forEach((prop) => {
    +				const qualifiedName = `${symbol.name}.${prop.name}`;
    +				checkPropertyName(prop.name, `name of ${qualifiedName}`);
    +				if ( prop.type ) {
    +					checkType(prop, `type of ${qualifiedName}`);
    +				}
    +			});
    +		}
    +		if ( symbol.methods ) {
    +			symbol.methods.forEach((method) => {
    +				const qualifiedName = `${symbol.name}.${method.name}`;
    +				checkName(method.name, `name of ${qualifiedName}`);
    +				checkMethodSignature(method, qualifiedName);
    +			});
    +		}
    +		if ( symbol.events ) {
    +			symbol.events.forEach((event) => {
    +				const qualifiedName = `${symbol.name}.${event.name}`;
    +				checkName(event.name, `name of ${qualifiedName}`);
    +				if ( event.parameters ) {
    +					event.parameters.forEach((param) => {
    +						checkParam(param, '', qualifiedName);
    +					});
    +				}
    +			});
    +		}
    +		// restore type parameters of outer scope
    +		currentTypeParameters = prevTypeParams;
    +	}
    +
    +	api.symbols.forEach((symbol) => {
    +		if ( symbol.kind === 'function' ) {
    +			checkCompoundName(symbol.name, `name of ${symbol.nam}`);
    +			checkMethodSignature(symbol, symbol.name);
    +		} else if ( symbol.kind === 'enum' ) {
    +			checkEnum(symbol);
    +		} else {
    +			checkClass(symbol);
    +		}
    +	});
    +
    +	if ( Object.keys(missing).length > 0 || Object.keys(naming).length > 0 ) {
    +		future("API validation errors:"); // TODO decide on level
    +
    +		Object.keys(missing).forEach((type) => {
    +			if ( Array.isArray(missing[type]) ) {
    +				if ( Object.hasOwn(erroneousTypes, type.toLowerCase()) ) {
    +					error(`type '${type}' (use '${erroneousTypes[type.toLowerCase()]}' instead)`);
    +					missing[type].forEach((usage) => error(`  ${usage}`));
    +				} else {
    +					future(`type '${type}'`);
    +					missing[type].forEach((usage) => future(`  ${usage}`));
    +				}
    +			}
    +		});
    +		Object.keys(naming).forEach((name) => {
    +			if ( Array.isArray(naming[name]) ) {
    +				future(`invalid name '${name}'`);
    +				naming[name].forEach((usage) => future(`  ${usage}`));
    +			}
    +		});
    +	} else {
    +		info("API validation succeeded.");
    +	}
    +
    +}
    +
    +//---- add on: API XML -----------------------------------------------------------------
    +
    +function createAPIXML(symbols, filename, options = {}) {
    +
    +	const roots = options.roots || null;
    +	const legacyContent = !!options.legacyContent;
    +	const omitDefaults = !!options.omitDefaults;
    +	const addRedundancy = !!options.resolveInheritance;
    +
    +	const sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    +	const ENUM = legacyContent ? "namespace" : "enum";
    +	const BASETYPE = legacyContent ? "baseType" : "extends";
    +	const PROPERTY = legacyContent ? "parameter" : "property";
    +
    +	const output = [];
    +	let indent = 0;
    +	let tags = [];
    +	let unclosedStartTag = false;
    +
    +	function getAPIJSON(name) {
    +
    +		const symbol = lookup(name);
    +		if ( symbol && !symbol.synthetic ) {
    +			return createAPIJSON4Symbol(symbol, false);
    +		}
    +		if ( addRedundancy && externalSymbols[name] ) {
    +			debug(`  using ${name} from external dependency`);
    +			return externalSymbols[name];
    +		}
    +		return symbol;
    +	}
    +
    +	function encode(s) {
    +		return s ? s.replace(/&/g, "&").replace(/ 0 ) {
    +			output.push(sIndent.slice(0,indent));
    +		}
    +		if ( arguments.length ) {
    +			for (let i = 0; i < arguments.length; i++) {
    +				output.push(arguments[i]);
    +			}
    +		}
    +		output.push("\n");
    +	}
    +
    +	function rootTag(name) {
    +		tags = [];
    +		unclosedStartTag = false;
    +		tag(name);
    +	}
    +
    +	function closeRootTag(name) {
    +		closeTag(name);
    +	}
    +
    +	function namespace(alias, namespace) {
    +		attrib(alias, namespace);
    +	}
    +
    +	function tag(name, value, omitEmpty) {
    +
    +		if ( omitEmpty && !value ) {
    +			return;
    +		}
    +		if ( unclosedStartTag ) {
    +			unclosedStartTag = false;
    +			write('>\n');
    +		}
    +		if ( arguments.length === 1 ) { // opening tag
    +			if ( indent > 0 ) {
    +				output.push(sIndent.slice(0,indent));
    +			}
    +			write("<", name);
    +			unclosedStartTag = true;
    +			if ( legacyContent ) {
    +				unclosedStartTag = false;
    +				write(">\n");
    +			}
    +			tags.push(name);
    +			indent++;
    +			return;
    +		}
    +		if ( value == null ) {
    +			writeln("<", name, "/>");
    +		} else {
    +			writeln("<", name, ">", encode(String(value)), "");
    +		}
    +	}
    +
    +	function attrib(name, value, defaultValue) {
    +		const emptyTag = arguments.length === 1;
    +		if ( omitDefaults && arguments.length === 3 && value === defaultValue ) {
    +			return;
    +		}
    +
    +		if ( !legacyContent ) {
    +			write(" " + name + "=\"");
    +			write(emptyTag ? "true" : encode(String(value)).replace(/"/g, """));
    +			write("\"");
    +		} else if ( emptyTag ) {
    +			writeln("<", name, "/>");
    +		} else {
    +			writeln("<", name, ">", encode(String(value)), "");
    +		}
    +	}
    +
    +	function closeTag(name, noIndent) {
    +
    +		indent--;
    +		const top = tags.pop();
    +		if ( top != name ) {
    +			// ERROR?
    +		}
    +
    +		if ( unclosedStartTag ) {
    +			unclosedStartTag = false;
    +			write("/>\n");
    +		} else if ( noIndent ) {
    +			write("\n");
    +		} else {
    +			writeln("");
    +		}
    +	}
    +
    +	function textContent(text) {
    +		if ( unclosedStartTag ) {
    +			unclosedStartTag = false;
    +			write('>');
    +		}
    +		write(encode(text));
    +	}
    +
    +	function tagWithSince(tagName, prop) {
    +		if ( prop ) {
    +			tag(tagName);
    +			if ( prop.since ) {
    +				attrib("since", prop.since);
    +			}
    +			if ( prop.text && prop.text.trim() ) {
    +				textContent(prop.text);
    +			}
    +			closeTag(tagName, true);
    +		}
    +	}
    +
    +	function isEnum(name) {
    +		const symbol = getAPIJSON(name);
    +		return !!symbol && symbol.kind === "enum";
    +	}
    +
    +	const replacement = {
    +		"\t": "\\t",
    +		"\n": "\\n",
    +		"\r": "\\r",
    +		"\"": "\\\"",
    +		"\\": "\\\\"
    +	};
    +
    +	/**
    +	 * Converts the given value into its JavaScript source code format.
    +	 *
    +	 * Strings will be enclosed in double quotes with special chars being escaped,
    +	 * all other values will be used 'literally'.
    +	 * @param {any} value Value to convert
    +	 * @param {string} type Data type of the value (used to detect enum values)
    +	 * @returns {string} The source code reprsentation of the value
    +	 */
    +	function toRaw(value, type) {
    +		if ( typeof value === "string" && !isEnum(type) ) {
    +			// eslint-disable-next-line no-control-regex
    +			return "\"" + value.replace(/[\u0000-\u001f"\\]/g,
    +				(c) => replacement[c] || "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0")) + "\"";
    +		}
    +		return value;
    +	}
    +
    +	function getAsString() {
    +		return output.join("");
    +	}
    +
    +	function writeMetadata(symbolAPI, inherited) {
    +
    +		const ui5Metadata = symbolAPI["ui5-metadata"];
    +		if ( !ui5Metadata ) {
    +			return;
    +		}
    +
    +		if ( addRedundancy && symbolAPI["extends"] ) {
    +			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			if ( baseSymbolAPI ) {
    +				writeMetadata(baseSymbolAPI, true);
    +			}
    +		}
    +
    +		if ( ui5Metadata.specialSettings ) {
    +			ui5Metadata.specialSettings.forEach((special) => {
    +				tag("specialSetting");
    +				attrib("name", special.name);
    +				attrib("type", special.type);
    +				attrib("visibility", special.visibility, 'public');
    +				if ( special.since ) {
    +					attrib("since", special.since);
    +				}
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", special.description, true);
    +				tagWithSince("experimental", special.experimental);
    +				tagWithSince("deprecated", special.deprecated);
    +				tag("methods", special.methods);
    +				closeTag("specialSetting");
    +			});
    +		}
    +
    +		if ( ui5Metadata.properties ) {
    +			ui5Metadata.properties.forEach((prop) => {
    +				tag("property");
    +				attrib("name", prop.name);
    +				attrib("type", prop.type, 'string');
    +				if ( prop.defaultValue !== null ) {
    +					attrib("defaultValue", toRaw(prop.defaultValue, prop.type));
    +				}
    +				attrib("visibility", prop.visibility, 'public');
    +				if ( prop.since ) {
    +					attrib("since", prop.since);
    +				}
    +				if ( prop.bindable ) {
    +					attrib("bindable", prop.bindable);
    +				}
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", prop.description, true);
    +				tagWithSince("experimental", prop.experimental);
    +				tagWithSince("deprecated", prop.deprecated);
    +				tag("methods", prop.methods);
    +				closeTag("property");
    +			});
    +		}
    +
    +		if ( ui5Metadata.defaultProperty ) {
    +			tag("defaultProperty", ui5Metadata.defaultProperty);
    +		}
    +
    +		if ( ui5Metadata.aggregations ) {
    +			ui5Metadata.aggregations.forEach((aggr) => {
    +				tag("aggregation");
    +				attrib("name", aggr.name);
    +				attrib("singularName", aggr.singularName); // TODO omit default?
    +				attrib("type", aggr.type, 'sap.ui.core.Control');
    +				if ( aggr.altTypes ) {
    +					attrib("altTypes", aggr.altTypes.join(","));
    +				}
    +				attrib("cardinality", aggr.cardinality, '0..n');
    +				attrib("visibility", aggr.visibility, 'public');
    +				if ( aggr.since ) {
    +					attrib("since", aggr.since);
    +				}
    +				if ( aggr.bindable ) {
    +					attrib("bindable", aggr.bindable);
    +				}
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", aggr.description, true);
    +				tagWithSince("experimental", aggr.experimental);
    +				tagWithSince("deprecated", aggr.deprecated);
    +				tag("methods", aggr.methods);
    +				closeTag("aggregation");
    +			});
    +		}
    +
    +		if ( ui5Metadata.defaultAggregation ) {
    +			tag("defaultAggregation", ui5Metadata.defaultAggregation);
    +		}
    +
    +		if ( ui5Metadata.associations ) {
    +			ui5Metadata.associations.forEach((assoc) => {
    +				tag("association");
    +				attrib("name", assoc.name);
    +				attrib("singularName", assoc.singularName); // TODO omit default?
    +				attrib("type", assoc.type, 'sap.ui.core.Control');
    +				attrib("cardinality", assoc.cardinality, '0..1');
    +				attrib("visibility", assoc.visibility, 'public');
    +				if ( assoc.since ) {
    +					attrib("since", assoc.since);
    +				}
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", assoc.description, true);
    +				tagWithSince("experimental", assoc.experimental);
    +				tagWithSince("deprecated", assoc.deprecated);
    +				tag("methods", assoc.methods);
    +				closeTag("association");
    +			});
    +		}
    +
    +		if ( ui5Metadata.events ) {
    +			ui5Metadata.events.forEach((event) => {
    +				tag("event");
    +				attrib("name", event.name);
    +				attrib("visibility", event.visibility, 'public');
    +				if ( event.since ) {
    +					attrib("since", event.since);
    +				}
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", event.description, true);
    +				tagWithSince("experimental", event.experimental);
    +				tagWithSince("deprecated", event.deprecated);
    +				if ( event.parameters ) {
    +					tag("parameters");
    +					for ( const pn in event.parameters ) {
    +						if ( event.parameters.hasOwnProperty(pn) ) {
    +							const param = event.parameters[pn];
    +
    +							tag("parameter");
    +							attrib("name", param.name);
    +							attrib("type", param.type);
    +							if ( param.since ) {
    +								attrib("since", param.since);
    +							}
    +							tag("description", param.description, true);
    +							tagWithSince("experimental", param.experimental);
    +							tagWithSince("deprecated", param.deprecated);
    +							closeTag("parameter");
    +						}
    +					}
    +					closeTag("parameters");
    +				}
    +				tag("methods", event.methods, true);
    +				closeTag("event");
    +			});
    +		}
    +
    +		if ( ui5Metadata.annotations ) {
    +			ui5Metadata.annotations.forEach((anno) => {
    +				tag("annotation");
    +				attrib("name", anno.name);
    +				attrib("namespace", anno.namespace); // TODO omit default?
    +				attrib("target", anno.target);
    +				attrib("annotation", anno.annotation);
    +				attrib("appliesTo", anno.appliesTo);
    +				if ( anno.since ) {
    +					attrib("since", anno.since);
    +				}
    +				tag("description", anno.description, true);
    +				tagWithSince("deprecated", anno.deprecated);
    +				closeTag("annotation");
    +			});
    +		}
    +
    +	}
    +
    +	function writeParameterPropertiesForMSettings(symbolAPI, inherited) {
    +
    +		const ui5Metadata = symbolAPI["ui5-metadata"];
    +		if ( !ui5Metadata ) {
    +			return;
    +		}
    +
    +		if ( symbolAPI["extends"] ) {
    +			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			writeParameterPropertiesForMSettings(baseSymbolAPI, true);
    +		}
    +
    +		if ( ui5Metadata.specialSettings ) {
    +			ui5Metadata.specialSettings.forEach((special) => {
    +				if ( special.visibility !== 'hidden' ) {
    +					tag("property");
    +					attrib("name", special.name);
    +					attrib("type", special.type);
    +					attrib("optional");
    +					if ( inherited ) {
    +						attrib("origin", symbolAPI.name);
    +					}
    +					tag("description", special.description, true);
    +					closeTag("property");
    +				}
    +			});
    +		}
    +
    +		if ( ui5Metadata.properties ) {
    +			ui5Metadata.properties.forEach((prop) => {
    +				tag("property");
    +				attrib("name", prop.name);
    +				attrib("type", prop.type);
    +				attrib("group", prop.group, 'Misc');
    +				if ( prop.defaultValue !== null ) {
    +					attrib("defaultValue", toRaw(prop.defaultValue, prop.type));
    +				}
    +				attrib("optional");
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", prop.description, true);
    +				closeTag("property");
    +			});
    +		}
    +
    +		if ( ui5Metadata.aggregations ) {
    +			ui5Metadata.aggregations.forEach((aggr) => {
    +				if ( aggr.visibility !== "hidden" ) {
    +					tag("property");
    +					attrib("name", aggr.name);
    +					attrib("type", aggr.type + (aggr.cardinality === '0..1' ? "" : "[]"));
    +					if ( aggr.altTypes ) {
    +						attrib("altTypes", aggr.altTypes.join(","));
    +					}
    +					attrib("optional");
    +					if ( inherited ) {
    +						attrib("origin", symbolAPI.name);
    +					}
    +					tag("description", aggr.description, true);
    +					closeTag("property");
    +				}
    +			});
    +		}
    +
    +		if ( ui5Metadata.associations ) {
    +			ui5Metadata.associations.forEach((assoc) => {
    +				if ( assoc.visibility !== "hidden" ) {
    +					tag("property");
    +					attrib("name", assoc.name);
    +					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === '0..1' ? "" : "[]"));
    +					attrib("optional");
    +					if ( inherited ) {
    +						attrib("origin", symbolAPI.name);
    +					}
    +					tag("description", assoc.description, true);
    +					closeTag("property");
    +				}
    +			});
    +		}
    +
    +		if ( ui5Metadata.events ) {
    +			ui5Metadata.events.forEach((event) => {
    +				tag("property");
    +				attrib("name", event.name);
    +				attrib("type", "function|array");
    +				attrib("optional");
    +				if ( inherited ) {
    +					attrib("origin", symbolAPI.name);
    +				}
    +				tag("description", event.description, true);
    +				closeTag("property");
    +			});
    +		}
    +
    +	}
    +
    +	function writeParameterProperties(param, paramName) {
    +		const props = param.parameterProperties,
    +			prefix = paramName + '.';
    +		let count = 0;
    +
    +		if ( props ) {
    +			for (const n in props ) {
    +				if ( props.hasOwnProperty(n) ) {
    +
    +					param = props[n];
    +
    +					if ( !legacyContent && count === 0 ) {
    +						tag("parameterProperties");
    +					}
    +
    +					count++;
    +
    +					tag(PROPERTY);
    +					attrib("name", legacyContent ? prefix + n : n);
    +					attrib("type", param.type);
    +					if ( param.since ) {
    +						attrib("since", param.since);
    +					}
    +					if ( param.optional ) {
    +						attrib("optional", param.optional);
    +					}
    +
    +					if ( !legacyContent ) {
    +						writeParameterProperties(param, prefix + n);
    +					}
    +
    +					tag("description", param.description, true);
    +					tagWithSince("experimental", param.experimental);
    +					tagWithSince("deprecated", param.deprecated);
    +
    +					closeTag(PROPERTY);
    +
    +					if ( legacyContent ) {
    +						writeParameterProperties(param, prefix + n);
    +					}
    +				}
    +			}
    +		}
    +
    +		if ( !legacyContent && count > 0 ) {
    +			closeTag("parameterProperties");
    +		}
    +	}
    +
    +	/*
    +	let rSplitSecTag = /^\s*\{([^\}]*)\}/;
    +
    +	function secTags($) {
    +		if ( !legacyContent ) {
    +			return;
    +		}
    +		const aTags = $.tags;
    +		if ( !aTags ) {
    +			return;
    +		}
    +		for (let iTag = 0; iTag < A_SECURITY_TAGS.length; iTag++  ) {
    +			const oTagDef = A_SECURITY_TAGS[iTag];
    +			for (let j = 0; j < aTags.length; j++ ) {
    +				if ( aTags[j].title.toLowerCase() === oTagDef.name.toLowerCase() ) {
    +					tag(oTagDef.name);
    +					const m = rSplitSecTag.exec(aTags[j].text);
    +					if ( m && m[1].trim() ) {
    +						const aParams = m[1].trim().split(/\s*\|\s* /); <-- remove the blank!
    +						for (let iParam = 0; iParam < aParams.length; iParam++ ) {
    +							tag(oTagDef.params[iParam], aParams[iParam]);
    +						}
    +					}
    +					const sDesc = aTags[j].description;
    +					tag("description", sDesc, true);
    +					closeTag(oTagDef.name);
    +				}
    +			}
    +		}
    +	}
    +	*/
    +
    +	function writeSymbol(symbol) {
    +
    +		if ( isFirstClassSymbol(symbol) && (roots || !symbol.synthetic) ) { // dump a symbol if it as a class symbol and if either hierarchies are dumped or if it is not a synthetic symbol
    +
    +			// for the hierarchy we use only the local information
    +			const symbolAPI = createAPIJSON4Symbol(symbol);
    +
    +			const kind = symbolAPI.kind === 'enum' ? ENUM : symbolAPI.kind;
    +
    +			tag(kind);
    +
    +			attrib("name", symbolAPI.name);
    +			attrib("basename", symbolAPI.basename);
    +//			if ( symbolAPI["resource"] ) {
    +//				attrib("resource");
    +//			}
    +			if ( symbolAPI["module"] ) {
    +				attrib("module", symbolAPI["module"]);
    +			}
    +			if ( symbolAPI["abstract"] ) {
    +				attrib("abstract");
    +			}
    +			if ( symbolAPI["final"] ) {
    +				attrib("final");
    +			}
    +			if ( symbolAPI["static"] ) {
    +				attrib("static");
    +			}
    +			attrib("visibility", symbolAPI.visibility, 'public');
    +			if ( symbolAPI.since ) {
    +				attrib("since", symbolAPI.since);
    +			}
    +			if ( symbolAPI["extends"] ) {
    +				tag(BASETYPE, symbolAPI["extends"]); // TODO what about multiple inheritance?
    +			}
    +			tag("description", symbolAPI.description, true);
    +			tagWithSince("experimental", symbolAPI.experimental);
    +			tagWithSince("deprecated", symbolAPI.deprecated);
    +
    +			if ( kind === 'class' ) {
    +
    +				const hasSettings = symbolAPI["ui5-metadata"];
    +
    +				if ( !legacyContent && symbolAPI["ui5-metadata"] ) {
    +
    +					tag("ui5-metadata");
    +
    +					if ( symbolAPI["ui5-metadata"].stereotype ) {
    +						attrib("stereotype", symbolAPI["ui5-metadata"].stereotype);
    +					}
    +
    +					writeMetadata(symbolAPI);
    +
    +					closeTag("ui5-metadata");
    +
    +				}
    +
    +				tag("constructor");
    +				if ( legacyContent ) {
    +					attrib("name", symbolAPI.basename);
    +				}
    +				attrib("visibility", symbolAPI.visibility, 'public');
    +				if ( symbolAPI.constructor.parameters ) {
    +					symbolAPI.constructor.parameters.forEach((param, j) => {
    +
    +						tag("parameter");
    +						attrib("name", param.name);
    +						attrib("type", param.type);
    +						attrib("optional", param.optional, false);
    +						if ( param.defaultValue !== undefined ) {
    +							attrib("defaultValue", param.defaultValue);
    +						}
    +						if ( param.since ) {
    +							attrib("since", param.since);
    +						}
    +
    +						if ( !legacyContent ) {
    +							if ( hasSettings && j === 1 && /setting/i.test(param.name) && /object/i.test(param.type) ) {
    +								if ( addRedundancy ) {
    +									tag("parameterProperties");
    +									writeParameterPropertiesForMSettings(symbolAPI);
    +									closeTag("parameterProperties");
    +								}
    +							} else {
    +								writeParameterProperties(param, param.name);
    +							}
    +						}
    +						tag("description", param.description, true);
    +						tagWithSince("experimental", param.experimental);
    +						tagWithSince("deprecated", param.deprecated);
    +						closeTag("parameter");
    +						if ( legacyContent ) {
    +							writeParameterProperties(param, param.name);
    +						}
    +					});
    +				}
    +
    +				tag("description", getConstructorDescription(symbol), true);
    +				// tagWithSince("experimental", symbol.experimental); // TODO repeat from class?
    +				// tagWithSince("deprecated", symbol.deprecated); // TODO repeat from class?
    +				// secTags(symbol); // TODO repeat from class?
    +				closeTag("constructor");
    +			}
    +
    +			/* TODO MIGRATE or remove, if not needed
    +			const ownSubspaces = ( symbol.__ui5.children || [] ).filter(($) => $.kind === 'namespace').sort(sortByAlias);
    +			for (let i = 0; i < ownSubspaces.length; i++) {
    +				const member = ownSubspaces[i];
    +				tag("namespace");
    +				tag("name", member.name);
    +				closeTag("namespace");
    +			}
    +			*/
    +
    +			if ( symbolAPI.properties ) {
    +				symbolAPI.properties.forEach((member) => {
    +					tag("property");
    +					attrib("name", member.name);
    +					if ( member.module ) {
    +						attrib("module", member.module);
    +					}
    +					attrib("visibility", member.visibility, 'public');
    +					if ( member["static"] ) {
    +						attrib("static");
    +					}
    +					if ( member.since ) {
    +						attrib("since", member.since);
    +					}
    +					attrib("type", member.type);
    +					tag("description", member.description, true);
    +					tagWithSince("experimental", member.experimental);
    +					tagWithSince("deprecated", member.deprecated);
    +					closeTag("property");
    +				});
    +			}
    +
    +			if ( symbolAPI.events ) {
    +				symbolAPI.events.forEach((member) => {
    +					tag("event");
    +					attrib("name", member.name);
    +					if ( member.module ) {
    +						attrib("module", member.module);
    +					}
    +					attrib("visibility", member.visibility, 'public');
    +					if ( member["static"] ) {
    +						attrib("static");
    +					}
    +					if ( member.since ) {
    +						attrib("since", member.since);
    +					}
    +
    +					if ( member.parameters ) {
    +						member.parameters.forEach((param) => {
    +
    +							tag("parameter");
    +							attrib("name", param.name);
    +							attrib("type", param.type);
    +							if ( param.since ) {
    +								attrib("since", param.since);
    +							}
    +							if ( !legacyContent ) {
    +								writeParameterProperties(param, param.name);
    +							}
    +							tag("description", param.description, true);
    +							tagWithSince("experimental", param.experimental);
    +							tagWithSince("deprecated", param.deprecated);
    +							closeTag("parameter");
    +							if ( legacyContent ) {
    +								writeParameterProperties(param, param.name);
    +							}
    +						});
    +					}
    +					tag("description", member.description, true);
    +					tagWithSince("experimental", member.experimental);
    +					tagWithSince("deprecated", member.deprecated);
    +					// TODO secTags(member);
    +					closeTag("event");
    +				});
    +			}
    +
    +			if ( symbolAPI.methods ) {
    +				symbolAPI.methods.forEach((member) => {
    +
    +					tag("method");
    +					attrib("name", member.name);
    +					if ( member.module ) {
    +						attrib("module", member.module);
    +					}
    +					attrib("visibility", member.visibility, 'public');
    +					if ( member["static"] ) {
    +						attrib("static");
    +					}
    +					if ( member.returnValue && member.returnValue.type  ) {
    +						let returnType = member.returnValue.type;
    +						if ( returnType === 'this' ) {
    +							returnType = symbolAPI.name;
    +						}
    +						attrib("type", returnType, 'void');
    +					}
    +					if ( member.since ) {
    +						attrib("since", member.since);
    +					}
    +
    +					if ( member.parameters ) {
    +						member.parameters.forEach((param) => {
    +
    +							tag("parameter");
    +							attrib("name", param.name);
    +							attrib("type", param.type);
    +							if ( param.optional ) {
    +								attrib("optional", param.optional);
    +							}
    +							if ( param.defaultValue !== undefined ) {
    +								attrib("defaultValue", param.defaultValue);
    +							}
    +							if ( param.since ) {
    +								attrib("since", param.since);
    +							}
    +							if ( !legacyContent ) {
    +								writeParameterProperties(param, param.name);
    +							}
    +							tag("description", param.description, true);
    +							tagWithSince("experimental", param.experimental);
    +							tagWithSince("deprecated", param.deprecated);
    +							closeTag("parameter");
    +							if ( legacyContent ) {
    +								writeParameterProperties(param, param.name);
    +							}
    +						});
    +					}
    +					tag("description", member.description, true);
    +					tagWithSince("experimental", member.experimental);
    +					tagWithSince("deprecated", member.deprecated);
    +					// TODO secTags(member);
    +					closeTag("method");
    +
    +				});
    +			}
    +
    +			if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
    +				tag("children");
    +				symbol.__ui5.children.forEach(writeSymbol);
    +				closeTag("children");
    +			}
    +
    +			closeTag(kind);
    +
    +		}
    +
    +	}
    +
    +	writeln("");
    +	rootTag("api");
    +	if ( !legacyContent ) {
    +		namespace("xmlns", "http://www.sap.com/sap.ui.library.api.xsd");
    +		attrib("_version", "1.0.0");
    +		if ( templateConf.version ) {
    +			attrib("version", templateConf.version.replace(/-SNAPSHOT$/,""));
    +		}
    +		if ( templateConf.uilib ) {
    +			attrib("library", templateConf.uilib);
    +		}
    +	}
    +
    +	if ( roots ) {
    +		roots.forEach(writeSymbol);
    +	} else {
    +		// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
    +		symbols.slice(0).sort(sortByAlias).forEach(writeSymbol);
    +	}
    +
    +	closeRootTag("api");
    +
    +	fs.mkPath(path.dirname(filename));
    +	fs.writeFileSync(filename, getAsString(), 'utf8');
    +}
    +
    +// Description + Settings
    +
    +function getConstructorDescription(symbol) {
    +	let description = symbol.description;
    +	const tags = symbol.tags;
    +	if ( tags ) {
    +		for (let i = 0; i < tags.length; i++) {
    +			if ( tags[i].title === "ui5-settings" && tags[i].text) {
    +				description += "\n

    \n" + tags[i].text; + break; + } + } + } + return description; +} + + +// Example + +function makeExample(example) { + const result = { + caption: null, + example: example + }; + const match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); + + if ( match ) { + result.caption = match[1]; + result.example = match[2]; + } + + return result; +} + +/* ---- exports ---- */ + +exports.publish = publish; diff --git a/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/typeParser.js b/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/typeParser.js new file mode 100644 index 00000000000..30bd9d59d8b --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/typeParser.js @@ -0,0 +1,499 @@ +/** + * Node script to parse type strings. + * + * (c) Copyright 2025 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +"use strict"; +class ASTBuilder { + literal(str) { + return { + type: "literal", + value: str + }; + } + simpleType(type) { + return { + type: "simpleType", + name: type + }; + } + array(componentType) { + return { + type: "array", + component: componentType + }; + } + object(keyType, valueType) { + return { + type: "object", + key: keyType, + value: valueType + }; + } + set(elementType) { + return { + type: "set", + element: elementType + }; + } + promise(fulfillmentType) { + return { + type: "promise", + fulfill: fulfillmentType + }; + } + "function"(paramTypes, returnType, thisType, constructorType) { + return { + "type": "function", + "params": paramTypes, + "return": returnType, + "this": thisType, + "constructor": constructorType + }; + } + structure(structure) { + return { + type: "structure", + fields: structure + }; + } + union(types) { + return { + type: "union", + types: types + }; + } + synthetic(type) { + type.synthetic = true; + return type; + } + nullable(type) { + type.nullable = true; + return type; + } + mandatory(type) { + type.mandatory = true; + return type; + } + optional(type) { + type.optional = true; + return type; + } + repeatable(type) { + type.repeatable = true; + return type; + } + typeApplication(type, templateTypes) { + return { + type: "typeApplication", + baseType: type, + templateTypes: templateTypes + }; + } +} + +function TypeParser(defaultBuilder = new ASTBuilder()) { + const rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|\.?<|>|\)|,|\[\]|\*|\?|!|=|\.\.\.)|\s*(false|true|(?:\+|-)?(?:\d+(?:\.\d+)?|NaN|Infinity)|'[^']*'|"[^"]*"|null|undefined)|\s*((?:module:)?\w+(?:[/.#~][$\w_]+)*)|./g; + + let input; + let builder; + let token; + let tokenStr; + + function next(expected) { + if ( expected !== undefined && token !== expected ) { + throw new SyntaxError( + `TypeParser: expected '${expected}', but found '${tokenStr}' ` + + `(pos: ${rLexer.lastIndex}, input='${input}')` + ); + } + const match = rLexer.exec(input); + if ( match ) { + tokenStr = match[1] || match[2] || match[3]; + token = match[1] || (match[2] && "literal") || (match[3] && "symbol"); + if ( !token ) { + throw new SyntaxError(`TypeParser: unexpected '${match[0]}' (pos: ${match.index}, input='${input}')`); + } + } else { + tokenStr = token = null; + } + } + + function parseType() { + let nullable = false; + let mandatory = false; + if ( token === "?" ) { + next(); + nullable = true; + } else if ( token === "!" ) { + next(); + mandatory = true; + } + + let type; + + if ( token === "literal" ) { + type = builder.literal(tokenStr); + next(); + } else if ( token === "Array.<" || token === "Array<" ) { + next(); + const componentType = parseTypes(); + next(">"); + type = builder.array(componentType); + } else if ( token === "Object.<" || token === "Object<" ) { + next(); + let keyType; + let valueType = parseTypes(); + if ( token === "," ) { + next(); + keyType = valueType; + valueType = parseTypes(); + } else { + keyType = builder.synthetic(builder.simpleType("string")); + } + next(">"); + type = builder.object(keyType, valueType); + } else if ( token === "Set.<" || token === "Set<" ) { + next(); + const elementType = parseTypes(); + next(">"); + type = builder.set(elementType); + } else if ( token === "Promise.<" || token === "Promise<" ) { + next(); + const resultType = parseTypes(); + next(">"); + type = builder.promise(resultType); + } else if ( token === "function(" ) { + next(); + let thisType; + let constructorType; + const paramTypes = []; + let returnType; + if ( tokenStr === "this" ) { + next(); + next(":"); + thisType = parseType(); + if ( token !== ")" ) { + next(","); + } + } else if ( tokenStr === "new" ) { + next(); + next(":"); + constructorType = parseType(); + if ( token !== ")" ) { + next(","); + } + } + while ( token !== ")" ) { + const repeatable = token === "..."; + if ( repeatable ) { + next(); + } + let paramType = parseTypes(); + if ( repeatable ) { + paramType = builder.repeatable(paramType); + } + const optional = token === "="; + if ( optional ) { + paramType = builder.optional(paramType); + next(); + } + paramTypes.push(paramType); + + // exit if there are no more parameters + if ( token !== "," ) { + break; + } + + if ( repeatable ) { + throw new SyntaxError( + `TypeParser: only the last parameter of a function can be repeatable ` + + `(pos: ${rLexer.lastIndex}, input='${input}')` + ); + } + + // consume the comma + next(); + } + next(")"); + if ( token === ":" ) { + next(":"); + returnType = parseType(); + } + type = builder.function(paramTypes, returnType, thisType, constructorType); + } else if ( token === "{" ) { + const structure = Object.create(null); + next(); + do { + const propName = tokenStr; + if ( !/^\w+$/.test(propName) ) { + throw new SyntaxError( + `TypeParser: structure field must have a simple name ` + + `(pos: ${rLexer.lastIndex}, input='${input}', field:'${propName}')` + ); + } + next("symbol"); + let propType; + const optional = token === "="; + if ( optional ) { + next(); + } + if ( token === ":" ) { + next(); + propType = parseTypes(); + } else { + propType = builder.synthetic(builder.simpleType("any")); + } + if ( optional ) { + propType = builder.optional(propType); + } + structure[propName] = propType; + if ( token === "}" ) { + break; + } + next(","); + } while (token); + next("}"); + type = builder.structure(structure); + } else if ( token === "(" ) { + next(); + type = parseTypes(); + next(")"); + } else if ( token === "*" ) { + next(); + type = builder.simpleType("*"); + } else { + type = builder.simpleType(tokenStr); + next("symbol"); + // check for suffix operators: either 'type application' (generics) or 'array', but not both of them + if ( token === "<" || token === ".<" ) { + next(); + const templateTypes = []; + do { + const templateType = parseTypes(); + templateTypes.push(templateType); + if ( token === ">" ) { + break; + } + next(","); + } while (token); + next(">"); + type = builder.typeApplication(type, templateTypes); + } else { + while ( token === "[]" ) { + next(); + type = builder.array(type); + } + } + } + if ( builder.normalizeType ) { + type = builder.normalizeType(type); + } + if ( nullable ) { + type = builder.nullable(type); + } + if ( mandatory ) { + type = builder.mandatory(type); + } + return type; + } + + function parseTypes() { + const types = []; + do { + types.push(parseType()); + if ( token !== "|" ) { + break; + } + next(); + } while (token); + return types.length === 1 ? types[0] : builder.union(types); + } + + this.parse = function(typeStr, tempBuilder = defaultBuilder) { + /* + try { + const r = catharsis.parse(typeStr, { jsdoc: true}); + console.log(JSON.stringify(typeStr, null, "\t"), r); + } catch (err) { + console.log(typeStr, err); + } + */ + builder = tempBuilder; + input = String(typeStr); + rLexer.lastIndex = 0; + next(); + const type = parseTypes(); + next(null); + return type; + }; + + /** + * Parses a string representing a complex type and returns an object with 2 fields: + * (1) simpleTypes: an array of the identified simple types inside the complex type; + * (2) template: a string indicating the position of the simple types in the original string. + * + * Examples: + * + * parseSimpleTypes("sap.ui.core.Control | null") returns + * { + * template: "${0} | ${1}", + * simpleTypes: ["sap.ui.core.Control", "null"] + * } + * + * parseSimpleTypes("Array|Array") returns + * { + * template: "Array<${0}>|Array<${1}>" + * simpleTypes: ["string", "number"], + * } + * + * parseSimpleTypes("Object") returns + * { + * template: "Object<${0},${1}>" + * simpleTypes: ["string", "number"], + * } + * + * parseSimpleTypes("function(sap.ui.base.Event, number): boolean") returns + * { + * template: "function(${0},${1}): ${2}" + * simpleTypes: ["sap.ui.base.Event", "number", "boolean"], + * } + * + * parseSimpleTypes("Promise") returns + * { + * template: "Promise<${0}>" + * simpleTypes: ["string"], + * } + * + * @param {string} sComplexType + * @param {function} [fnFilter] optional filter function to be called for each simple type found. If a type is filtered out, it will not be added to the list of simple types, but will be present in its original form in the template. + * @returns {{simpleTypes: string[], template: string}} an object with the properties template and simpleTypes + */ + this.parseSimpleTypes = function(sComplexType, fnFilter) { + const parsed = this.parse(sComplexType , new ASTBuilder() ); + let iIndexOfNextSimpleType = 0; + + function processSimpleType(sType) { + var bSkip = fnFilter && !fnFilter(sType); + if (bSkip) { + return { + template: sType, + simpleTypes: [] // do not add this type to the list of parsed types + }; + } + + return { + template: "${" + iIndexOfNextSimpleType++ + "}", + simpleTypes: [sType] // add this type to the list of parsed types + }; + } + + function findSimpleTypes(parsed) { + + /* eslint-disable no-case-declarations */ + switch (parsed.type) { + case "simpleType": + return processSimpleType(parsed.name); + case "literal": + return processSimpleType(parsed.value); + case "array": + const component = findSimpleTypes(parsed.component); + return { + template: "Array<" + component.template + ">", + simpleTypes: component.simpleTypes + }; + case "object": + const key = findSimpleTypes(parsed.key); + const value = findSimpleTypes(parsed.value); + return { + template: "Object<" + key.template + "," + value.template + ">", + simpleTypes: key.simpleTypes.concat(value.simpleTypes) + }; + case "function": + const aParamTemplates = []; + let aParamsimpleTypes = []; + if (Object.hasOwn(parsed, "constructor") && parsed.constructor) { + const types = findSimpleTypes(parsed.constructor); + aParamTemplates.push("new:" + types.template); + aParamsimpleTypes = aParamsimpleTypes.concat(types.simpleTypes); + } + if (parsed.this) { + const types = findSimpleTypes(parsed.this); + aParamTemplates.push("this:" + types.template); + aParamsimpleTypes = aParamsimpleTypes.concat(types.simpleTypes); + } + parsed.params.forEach(function(paramType) { + const types = findSimpleTypes(paramType); + aParamTemplates.push(types.template + (paramType.optional ? "?" : "")); + aParamsimpleTypes = aParamsimpleTypes.concat(types.simpleTypes); + }); + const returnType = parsed.return ? findSimpleTypes(parsed.return) : {simpleTypes: []}; + const returnTemplate = returnType.template ? " : " + returnType.template : ""; + const finalTemplate = "function(" + aParamTemplates.join(",") + ")" + returnTemplate; + return { + template: finalTemplate, + simpleTypes: aParamsimpleTypes.concat(returnType.simpleTypes) + }; + case "union": + const unionParts = parsed.types, aPartsTemplates = []; + let aPartsSimpleTypes = []; + unionParts.forEach(function (part) { + const types = findSimpleTypes(part); + aPartsTemplates.push(types.template); + aPartsSimpleTypes = aPartsSimpleTypes.concat(types.simpleTypes); + }); + return { + template: aPartsTemplates.join(" | "), + simpleTypes: aPartsSimpleTypes + }; + case "promise": + const fulfill = findSimpleTypes(parsed.fulfill); + return { + template: "Promise<" + fulfill.template + ">", + simpleTypes: fulfill.simpleTypes + }; + case "set": + const element = findSimpleTypes(parsed.element); + return { + template: "Set<" + element.template + ">", + simpleTypes: element.simpleTypes + }; + case "typeApplication": + const baseType = findSimpleTypes(parsed.baseType); + const templateTypes = parsed.templateTypes.map(findSimpleTypes); + return { + template: baseType.template + "<" + templateTypes.map(function (type) { + return type.template; + }).join(",") + ">", + simpleTypes: baseType.simpleTypes.concat(templateTypes.reduce(function (a, b) { + return a.concat(b.simpleTypes); + }, [])) + }; + case "structure": + const aFields = []; + let aSimpleTypes = []; + Object.keys(parsed.fields).forEach(function (sKey) { + const oField = parsed.fields[sKey]; + const types = findSimpleTypes(oField); + aFields.push(sKey + ":" + types.template); + aSimpleTypes = aSimpleTypes.concat(types.simpleTypes); + }); + return { + template: "{" + aFields.join(",") + "}", + simpleTypes: aSimpleTypes + }; + } + /* eslint-enable no-case-declarations */ + } + + return findSimpleTypes(parsed); + }; +} + + +module.exports = { + ASTBuilder, + TypeParser +}; diff --git a/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/versionUtil.js b/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/versionUtil.js new file mode 100644 index 00000000000..8623a230b69 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/lib/ui5/template/utils/versionUtil.js @@ -0,0 +1,136 @@ +"use strict"; + +// [m][.minor][.patch ] [pre-release][ build-info ] +const rSemVer = /^\d+(?:\.\d+(?:\.\d+)?)?(?:-[\w\-]+)?(?:\+[\w\-]+)?$/; + +function isSemVer(str) { + return rSemVer.test(str); +} + +const rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(\.$|\.\s+|[,:;]\s*|\s-\s*|\s|$)/i; +function _parseVersion(value) { + const m = rSinceVersion.exec(value); + if (m) { + // 3rd capture group contains additional characters such as ,: - + // version is either at its own or text is separated by space + var versionFollowedBySpace = m[3] === "" || /^\s/.test(m[3]); + return { + version: m[1], + versionFollowedBySpace: versionFollowedBySpace, + nextPosition: m[0].length + }; + } + return undefined; +} + +/** + * Extracts version + * + * @example valid versions + * "1.33.4 " + * "1.334" + */ +function extractVersion(value) { + + if ( !value ) { + return undefined; + } + + if ( value === true ) { + value = ''; + } else { + value = String(value); + } + + const parseResult = _parseVersion(value); + if (parseResult && parseResult.versionFollowedBySpace) { + return parseResult.version; + } + return undefined; +} + +const rSinceIndicator = /^(?:as\s+of|since)(?:\s+version)?\s*/i; + +/** + * Extracts since information from given value. + * + * + * pos: position of additional text + * since: version information + * text: additional text + * + * + * @example version indication with valid version + * Input: "Since version 1.3.4. mytext" + * Output: + * { pos: 21, since: '1.3.4', value: 'mytext' } + * + * @example version indication without valid version + * Input: "Since version mytext" + * Output: + * { pos: 0, since: null, value: 'Since version mytext.' } + * + * @example no indicator and no version present + * Input: "mytext" + * Output: + * { pos: 0, value: 'mytext.' } + * + * + * + * @param {string|boolean} value + * @returns {{pos: number, value: string, since: string|null|undefined}} + * undefined: value is falsy + */ +function extractSince(value) { + + if ( !value ) { + return undefined; + } + + if ( value === true ) { + value = ''; + } else { + value = String(value); + } + + if (isSemVer(value)) { + return { + since: value, + pos: value.length, + value: "" + }; + } + + const mSinceIndicator = rSinceIndicator.exec(value); + if (mSinceIndicator) { + const iSinceIndicatorLength = mSinceIndicator[0].length; + const versionAndText = value.substring(iSinceIndicatorLength); + const parseResult = _parseVersion(versionAndText); + if ( parseResult ) { + const textPosition = iSinceIndicatorLength + parseResult.nextPosition; + return { + since : parseResult.version, + pos : textPosition, + value : value.slice(textPosition).trim() + }; + } + // since indicator present but version cannot be extracted + return { + since: null, + pos : 0, + value: value.trim() + }; + } + + return { + pos : 0, + value: value.trim() + }; + +} + +module.exports = { + isSemVer, + extractSince, + extractVersion +}; diff --git a/packages/builder/lib/processors/jsdoc/sdkTransformer.js b/packages/builder/lib/processors/jsdoc/sdkTransformer.js new file mode 100644 index 00000000000..874a31fa757 --- /dev/null +++ b/packages/builder/lib/processors/jsdoc/sdkTransformer.js @@ -0,0 +1,46 @@ +import {createResource} from "@ui5/fs/resourceFactory"; +import transformer from "./lib/transformApiJson.js"; + +/** + * @public + * @module @ui5/builder/processors/jsdoc/sdkTransformer + */ + +/** + * Transform api.json as created by [jsdocGenerator]{@link @ui5/builder/processors/jsdoc/jsdocGenerator} + * for usage in a UI5 SDK + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {string} parameters.apiJsonPath Path to the projects api.json file as created by + * [jsdocGenerator]{@link @ui5/builder/processors/jsdoc/jsdocGenerator} + * @param {string} parameters.dotLibraryPath Path to the projects .library file + * @param {string[]} parameters.dependencyApiJsonPaths List of paths to the api.json files of all dependencies of + * the project as created by [jsdocGenerator]{@link @ui5/builder/processors/jsdoc/jsdocGenerator} + * @param {string} parameters.targetApiJsonPath Path to create the new, transformed api.json resource for + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or + * custom [fs interface]{@link module:@ui5/fs/fsInterface} to use + * + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with created resources + */ +const sdkTransformer = async function({ + apiJsonPath, dotLibraryPath, dependencyApiJsonPaths, targetApiJsonPath, fs} = {} +) { + if (!apiJsonPath || !dotLibraryPath || !targetApiJsonPath || !dependencyApiJsonPaths || !fs) { + throw new Error("[sdkTransformer]: One or more mandatory parameters not provided"); + } + const fakeTargetPath = "/ignore/this/path/resource/will/be/returned"; + const apiJsonContent = await transformer(apiJsonPath, fakeTargetPath, dotLibraryPath, dependencyApiJsonPaths, "", { + fs, + returnOutputFiles: true + }); + return [createResource({ + path: targetApiJsonPath, + string: apiJsonContent + })]; +}; + +export default sdkTransformer; diff --git a/packages/builder/lib/processors/libraryLessGenerator.js b/packages/builder/lib/processors/libraryLessGenerator.js new file mode 100644 index 00000000000..9903b10ccb4 --- /dev/null +++ b/packages/builder/lib/processors/libraryLessGenerator.js @@ -0,0 +1,184 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:libraryLessGenerator"); + +import {promisify} from "node:util"; +import posixPath from "node:path/posix"; +import Resource from "@ui5/fs/Resource"; + +const IMPORT_PATTERN = /@import .*"(.*)";/g; +const BASE_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/base\.less$/; +const GLOBAL_LESS_PATTERN = /^\/resources\/sap\/ui\/core\/themes\/([^/]+)\/global\.less$/; + +class LibraryLessGenerator { + constructor({fs}) { + const readFile = promisify(fs.readFile); + this.readFile = async (filePath) => readFile(filePath, {encoding: "utf8"}); + } + async generate({filePath, fileContent}) { + return `/* NOTE: This file was generated as an optimized version of ` + + `"library.source.less" for the Theme Designer. */\n\n` + + (await this.resolveLessImports({ + filePath, + fileContent + })); + } + static getPathToRoot(baseDir) { + return posixPath.relative(baseDir, "/") + "/"; + } + async resolveLessImports({filePath, fileContent}) { + const imports = this.findLessImports(fileContent); + if (!imports.length) { + // Skip processing when no imports are found + return fileContent; + } + const replacements = await Promise.all(imports.map(async (importMatch) => { + const baseDir = posixPath.dirname(filePath); + const resolvedFilePath = posixPath.resolve(baseDir, importMatch.path); + importMatch.content = await this.resolveLessImport(importMatch.path, resolvedFilePath, baseDir); + return importMatch; + })); + + // Apply replacements in reverse order to not modify the relevant indices + const array = Array.from(fileContent); + for (let i = replacements.length - 1; i >= 0; i--) { + const replacement = replacements[i]; + if (!replacement.content) { + continue; + } + array.splice( + /* index */ replacement.matchStart, + /* count */ replacement.matchLength, + /* insert */ replacement.content + ); + } + return array.join(""); + } + async resolveLessImport(originalFilePath, resolvedFilePath, baseDir) { + // Rewrite base.less imports + const baseLessMatch = BASE_LESS_PATTERN.exec(resolvedFilePath); + if (baseLessMatch) { + let baseLessThemeName = baseLessMatch[1]; + if (baseLessThemeName === "base") { + baseLessThemeName = "baseTheme"; + } + + const baseLessPath = LibraryLessGenerator.getPathToRoot(baseDir) + + "Base/baseLib/" + baseLessThemeName + "/base.less"; + return "@import \"" + baseLessPath + "\"; /* ORIGINAL IMPORT PATH: \"" + originalFilePath + "\" */\n"; + } + + // Rewrite library imports to correct file name + if (posixPath.basename(resolvedFilePath) === "library.source.less") { + return `@import "${originalFilePath.replace(/library\.source\.less$/, "library.less")}";`; + } + + // Excluding global.less within sap.ui.core + // It must be imported by the Theme Designer (also see declaration in sap/ui/core/.theming) + if (GLOBAL_LESS_PATTERN.test(resolvedFilePath)) { + return null; + } + + /* + * Throw error in case of files which are not in the same directory as the current file because + * inlining them would break relative URLs. + * A possible solution would be to rewrite relative URLs when inlining the content. + * + * Keeping the import will cause errors since only "library.less" and "global.less" are + * configured to be available to the Theme Designer (.theming generated in generateThemeDesignerResources). + */ + const relativeFilePath = posixPath.relative(baseDir, resolvedFilePath); + if (relativeFilePath.includes(posixPath.sep)) { + throw new Error( + `Could not inline import '${resolvedFilePath}' outside of theme directory '${baseDir}'. ` + + `Stylesheets must be located in the theme directory (no sub-directories).` + ); + } + + let importedFileContent; + try { + importedFileContent = await this.readFile(resolvedFilePath); + } catch (err) { + if (err.code === "ENOENT") { + throw new Error( + `libraryLessGenerator: Unable to resolve import '${originalFilePath}' from '${baseDir}'\n` + + err.message + ); + } else { + throw err; + } + } + return `/* START "${originalFilePath}" */\n` + + (await this.resolveLessImports({ + filePath: resolvedFilePath, + fileContent: importedFileContent + })) + + `\n/* END "${originalFilePath}" */\n`; + } + findLessImports(fileContent) { + const imports = []; + let match; + while ((match = IMPORT_PATTERN.exec(fileContent)) !== null) { + imports.push({ + path: match[1], + matchStart: match.index, + matchLength: match[0].length + }); + } + return imports; + } +} + +/** + * @public + * @module @ui5/builder/processors/libraryLessGenerator + */ + +/** + * Creates a "library.less" file for the SAP Theme Designer based on a "library.source.less" file. + * + *

      + *
    • Bundles all *.less file of the theme by replacing the import with the corresponding file content
    • + *
    • Imports to "base.less" are adopted so that they point to the "BaseLib" that is available within + * the Theme Designer infrastructure
    • + *
    • Imports to "global.less" are kept as they should not be bundled
    • + *
    • Imports to "library.source.less" are adopted to "library.less"
    • + *
    + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of library.source.less + * resources + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom + * [fs interface]{@link module:@ui5/fs/fsInterface} + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with library.less resources + */ +async function createLibraryLess({resources, fs}) { + const generator = new LibraryLessGenerator({fs}); + return Promise.all(resources.map(async (librarySourceLessResource) => { + const filePath = librarySourceLessResource.getPath(); + const fileContent = await librarySourceLessResource.getString(); + + log.verbose(`Generating library.less file based on ${filePath}`); + + const libraryLessFileContent = await generator.generate({filePath, fileContent}); + const libraryLessFilePath = posixPath.join(posixPath.dirname(filePath), "library.less"); + + return new Resource({ + path: libraryLessFilePath, + string: libraryLessFileContent + }); + })); +} + +export default createLibraryLess; + +let myLibraryLessGenerator; +// Export class for testing only +/* istanbul ignore else */ +if (process.env.NODE_ENV === "test") { + myLibraryLessGenerator = LibraryLessGenerator; +} +export const _LibraryLessGenerator = myLibraryLessGenerator; diff --git a/packages/builder/lib/processors/manifestCreator.js b/packages/builder/lib/processors/manifestCreator.js new file mode 100644 index 00000000000..09f00c27c67 --- /dev/null +++ b/packages/builder/lib/processors/manifestCreator.js @@ -0,0 +1,656 @@ + +import posixPath from "node:path/posix"; +import semver from "semver"; +const {SemVer: Version} = semver; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:manifestCreator"); +import Resource from "@ui5/fs/Resource"; +import xml2js from "xml2js"; +import analyzeLibraryJS from "../lbt/analyzer/analyzeLibraryJS.js"; + +/* + * A single parse instance to be used by all invocations (TODO check reentrance capa of xml2js) + */ +const parser = new xml2js.Parser({ + // explicitChildren: true, + preserveChildrenOrder: true, + xmlns: true +}); + +// const APP_DESCRIPTOR_V3 = new Version("1.2.0"); +const APP_DESCRIPTOR_V3_SECTION_SAP_APP = new Version("1.2.0"); +const APP_DESCRIPTOR_V3_OTHER_SECTIONS = new Version("1.1.0"); +const APP_DESCRIPTOR_V5 = new Version("1.4.0"); +const APP_DESCRIPTOR_V10 = new Version("1.9.0"); +const APP_DESCRIPTOR_V22 = new Version("1.21.0"); + +// namespaces used in .library files +const XMLNS_UILIB = "http://www.sap.com/sap.ui.library.xsd"; +const XMLNS_OWNERSHIP = "http://www.sap.com/ui5/buildext/ownership"; +const XMLNS_MANIFEST = "http://www.sap.com/ui5/buildext/manifest"; +const XMLNS_THIRDPARTY = "http://www.sap.com/ui5/buildext/thirdparty"; + +function getAttribute(node, attr) { + return (node.$ && node.$[attr] && node.$[attr].value) || null; +} + +function getBooleanAttribute(node, attr) { + return getAttribute(node, attr) === "true"; +} + +function findChild(node, tagName, namespaceURI) { + if ( node && + Array.isArray(node[tagName]) && + node[tagName].length > 0 && + (namespaceURI == null || (node[tagName][0].$ns && node[tagName][0].$ns.uri === namespaceURI)) ) { + return node[tagName][0]; + } +} + +function findChildren(node, tagName, namespaceURI) { + const children = node && node[tagName]; + if ( Array.isArray(children) ) { + return children.filter((child) => (namespaceURI == null || (child.$ns && child.$ns.uri === namespaceURI))); + } + return []; +} + +function getChildTextContent(node, tagName, defaultValue) { + const child = findChild(node, tagName); + return child ? (child._ || "") : defaultValue; +} + +class Dependency { + constructor(xml) { + this.xml = xml; + } + + getLibraryName() { + return getChildTextContent(this.xml, "libraryName"); + } + + getVersion() { + return getChildTextContent(this.xml, "version"); + } + + isLazy() { + return "true" === getChildTextContent(this.xml, "lazy"); + } +} + +class Library { + constructor(xml) { + this.xml = xml; + } + + getVersion() { + return getChildTextContent(this.xml, "version"); + } + + getName() { + return getChildTextContent(this.xml, "name"); + } + + getTitle() { + return getChildTextContent(this.xml, "title"); + } + + getDocumentation() { + return getChildTextContent(this.xml, "documentation"); + } + + getDependencies() { + const container = findChild(this.xml, "dependencies"); + const deps = findChildren(container, "dependency") || []; + return deps.map((dep) => new Dependency(dep)); + } + + getAppData(tagName, namespace) { + const appData = findChild(this.xml, "appData", XMLNS_UILIB); + return findChild(appData, tagName, namespace); + } + + static async from(resource) { + const content = await resource.getString(); + return new Promise( (resolve, reject) => { + parser.parseString(content, (err, xml) => { + if ( err ) { + reject(err); + return; + } + resolve(new Library(xml.library)); + }); + }); + } +} + + +class LibraryBundle { + /** + * + * @param {string} prefix + * @param {@ui5/fs/Resource[]} resources + */ + constructor(prefix, resources) { + this.prefix = prefix; + this.resources = resources.filter((res) => res.getPath().startsWith(prefix)); + } + + /** + * + * @param {string} name + * @returns {@ui5/fs/Resource} + */ + findResource(name) { + return this.resources.find((res) => res.getPath() === this.prefix + name); + } + + /** + * + * @param {RegExp} pattern + * @returns {@ui5/fs/Resource[]} + */ + getResources(pattern) { + return this.resources.filter((res) => pattern == null || pattern.test(res.getPath())); + } +} + +/* + * Creates the library manifest.json file for a UILibrary. + */ +async function createManifest( + libraryResource, libBundle, descriptorVersion, _include3rdParty, omitMinVersions, getProjectVersion +) { + // create a Library wrapper around the .library XML + const library = await Library.from(libraryResource); + + const libraryPathPrefix = posixPath.dirname(libraryResource.getPath()) + "/"; + + // collect information from library.js file + const libraryJSInfo = await analyzeLibraryJS(libBundle.findResource("library.js")); + const includeSupportedLocalesInformation = descriptorVersion.compare(APP_DESCRIPTOR_V22) >= 0; + /** + * cache for supported locales + * + * @see createI18nSection + */ + const i18nToSupportedLocales = new Map(); + + const manifestAppData = library.getAppData("manifest", XMLNS_MANIFEST); + const sapFioriAppData = findChild(manifestAppData, "sap.fiori"); + + function sectionVersion(candidateVersion) { + // _version property for nested sections became optional with AppDescriptor V5 + if ( descriptorVersion.compare(APP_DESCRIPTOR_V5) < 0 ) { + return candidateVersion.toString(); + } + // return undefined + } + + function createSapApp() { + function hasManifest(componentPath) { + const manifestPath = componentPath + "/manifest.json"; + + const manifestResource = libBundle.findResource(manifestPath.substring(libraryPathPrefix.length)); + if ( manifestResource == null ) { + log.verbose(" Component has no accompanying manifest.json, don't list it as 'embedded'"); + return false; + } + return true; + } + + function findEmbeddedComponents() { + const result = []; + const components = libBundle.getResources(/^\/(?:[^/]+\/)*Component\.js$/); + for (const comp of components) { + const componentPath = posixPath.dirname(comp.getPath()); + log.verbose(`Checking component at ${componentPath}`); + if ( componentPath.startsWith(libraryPathPrefix) && hasManifest(componentPath) ) { + result.push( componentPath.substring(libraryPathPrefix.length) ); + } else if ( libraryPathPrefix === "/resources/sap/apf/" ) { + log.verbose(`Package ${componentPath} contains both '*.library' and 'Component.js'. ` + + `This is a known issue but can't be solved due to backward compatibility.`); + } else if ( + libraryPathPrefix === (componentPath + "/") && + libraryPathPrefix !== "/resources/sap/ui/core/" + ) { + log.error(`Package ${componentPath} contains both '*.library' and 'Component.js'. ` + + `This is not supported by manifests, therefore the component won't be ` + + `listed in the library's manifest.`); + } + } + return result.sort(); + } + + function isValid(version) { + return version && version !== "@version@" && version !== "${version}"; + } + + function getLibraryTitle() { + if ( library.getTitle() ) { + return library.getTitle(); + } + if ( library.getDocumentation() ) { + let desc = library.getDocumentation(); + // remove all tags + let prevDesc; + do { // Safely strip tags (https://codeql.github.com/codeql-query-help/javascript/js-incomplete-multi-character-sanitization/#example) + prevDesc = desc; + desc = desc.replace(/\s+/g, " ").replace(/<\/?[a-zA-Z][a-zA-Z0-9_$.]*(\s[^>]*)>/g, ""); + } while (prevDesc !== desc); + // extract summary (first sentence) + const m = /^([\w\W]+?[.;!?])[^a-zA-Z0-9_$]/.exec(desc); + return m ? m[1] : desc; + } + return library.getName(); + } + + function getDefaultACH() { + const ownership = library.getAppData("ownership", XMLNS_OWNERSHIP); + for (const comp of findChildren(ownership, "component")) { + if ( comp._ ) { + return comp._; + } + } + } + + function offline() { + let result = sapFioriAppData == null ? true : false; + const offlineElement = findChild(manifestAppData, "offline"); + if ( offlineElement ) { + result = offlineElement._ === "true"; + } + return result; + } + + function sourceTemplate() { + const sourceTemplateElement = findChild(manifestAppData, "sourceTemplate"); + if ( sourceTemplateElement ) { + return { + "id": getChildTextContent(sourceTemplateElement, "id"), + "version": getChildTextContent(sourceTemplateElement, "version") + }; + } + } + + function openSourceComponents() { + const embeddedOSComponents = new Set(); + const osComponents = []; + for (const osCompElem of findChildren(manifestAppData, "openSourceComponent")) { + const name = getAttribute(osCompElem, "name"); + const packagedWithMySelf = getBooleanAttribute(osCompElem, "packagedWithMySelf"); + osComponents.push({ + name: name, + packagedWithMySelf, + version: packagedWithMySelf ? getAttribute(osCompElem, "version") : undefined + }); + if ( packagedWithMySelf ) { + embeddedOSComponents.add(name); + } + } + + if ( _include3rdParty ) { + // also merge all thirdparty libs, but only with the name - version info is not available + // only merge in if no lib with the same name has been declared already + const thirdpartyAppData = library.getAppData("thirdparty", XMLNS_THIRDPARTY); + for (const thirdPartyElem of findChildren(thirdpartyAppData, "lib")) { + const osCompName = getAttribute(thirdPartyElem, "name"); + if ( !embeddedOSComponents.has(osCompName) ) { + embeddedOSComponents.add(osCompName); + osComponents.push({ + name: osCompName, + packagedWithMySelf: true, + version: getAttribute(thirdPartyElem, "version") || "0.0.0" + }); + } + } + } + + return osComponents.length > 0 ? osComponents : undefined; + } + const i18nText = getChildTextContent(manifestAppData, "i18n"); + let i18n; + if (typeof i18nText === "string") { + i18n = createI18nSection(i18nText, i18nToSupportedLocales); + log.verbose(`sap.app/i18n taken from .library appData: '${i18nText}'`); + } + const sapApp = { + _version: sectionVersion(APP_DESCRIPTOR_V3_SECTION_SAP_APP), + id: library.getName(), + type: "library", + embeds: findEmbeddedComponents(), + i18n, + applicationVersion: { + version: isValid(library.getVersion()) ? library.getVersion() : getProjectVersion(library.getName()) + }, + title: getLibraryTitle(), + description: library.getDocumentation(), + ach: getDefaultACH(), // optional, might be undefined + resources: "resources.json", + offline: offline(), + sourceTemplate: sourceTemplate(), + openSourceComponents: openSourceComponents() + }; + + log.verbose(` sap.app/id taken from .library: '${sapApp.id}'`); + log.verbose(` sap.app/embeds determined from resources:`, sapApp.embeds); + log.verbose(` sap.app/i18n taken from .library appData:`, sapApp.i18n); + log.verbose(` sap.app/ach taken from .library appData/ownership: '${sapApp.ach}'`); + + return sapApp; + } + + function createSapUi() { + function deviceTypes() { + const deviceTypesElement = findChild(manifestAppData, "deviceTypes"); + if ( deviceTypesElement ) { + return { + desktop: getBooleanAttribute(deviceTypesElement, "desktop"), + tablet: getBooleanAttribute(deviceTypesElement, "tablet"), + phone: getBooleanAttribute(deviceTypesElement, "phone"), + }; + } + } + + function collectThemes() { + const themes = Object.create(null); + + // find theme resources and determine theme names from their paths + libBundle.getResources(/(?:[^/]+\/)*themes\//).forEach((res) => { + if ( !res.getPath().startsWith(libraryPathPrefix + "themes/") ) { + // only consider themes within direct "themes" sub-directory + return; + } + const match = /\/themes\/([^/]+)\//.exec(res.getPath()); + if ( match ) { + themes[match[1]] = true; + } + }); + + // merge with supportedTheme info from .library file + const elems = findChildren(manifestAppData, "supportedTheme"); + if ( elems ) { + elems.forEach((elem) => { + if ( elem._ ) { + themes[elem._]; + } + }); + } + return Object.keys(themes).sort(); + } + + const sapUi = { + _version: sectionVersion(APP_DESCRIPTOR_V3_OTHER_SECTIONS), + technology: "UI5", + deviceTypes: deviceTypes(), + supportedThemes: collectThemes() + }; + + log.verbose(` sap.ui/supportedThemes determined from resources: '${sapUi.supportedThemes.join(", ")}'`); + + return sapUi; + } + + function createSapUI5() { + function getUI5Version() { + return normalizeVersion(getProjectVersion("sap.ui.core")); + } + + function dependencies() { + const dependencies = { + minUI5Version: omitMinVersions ? "" : getUI5Version(), + libs: { + } + }; + if ( library.getDependencies() != null ) { + for (const dep of library.getDependencies()) { + dependencies.libs[dep.getLibraryName()] = { + minVersion: omitMinVersions ? "" : getProjectVersion(dep.getLibraryName()), + lazy: dep.isLazy() || undefined // suppress default (false) + }; + } + } + log.verbose(` sap.ui5/dependencies/libs determined from .library dependencies:`, dependencies.libs); + return dependencies; + } + + function contentDensities() { + const contentDensitiesElement = findChild(manifestAppData, "contentDensities"); + if ( contentDensitiesElement != null ) { + const contentDensities = { + cozy: getBooleanAttribute(contentDensitiesElement, "cozy"), + compact: getBooleanAttribute(contentDensitiesElement, "compact") + }; + log.verbose(` sap.ui5/contentDensities property taken from .library appData:`, contentDensities); + return contentDensities; + } + } + + function createLibraryMetadata() { + if ( descriptorVersion.compare(APP_DESCRIPTOR_V10) < 0 ) { + log.verbose(` Target descriptor version ${descriptorVersion}: skipping sap.ui5/library information`); + } + + log.verbose(` Target descriptor version ${descriptorVersion}: include sap.ui5/library information`); + + const sapUi5AppData = findChild(manifestAppData, "sap.ui5"); + const libraryAppData = findChild(sapUi5AppData, "library"); + + // i18n: + // - from .library/appData/manifest/sap.ui5/library/i18n + // - from library resources (if "messagebundle.properties" exists) + function i18n() { + let i18n = getChildTextContent(libraryAppData, "i18n"); + if ( typeof i18n === "string") { + if ( i18n === "false" ) { + return false; + } else if ( i18n === "true" ) { + i18n = "messagebundle.properties"; + } + // log.verbose(" sap.ui5/library/i18n property taken from .library appData: '%s'", library.i18n); + } else { + if ( libBundle.findResource("messagebundle.properties") != null ) { + // log.verbose(" sap.ui5/library/i18n property determined from resources: '%s'", library.i18n); + i18n = "messagebundle.properties"; + } else { + // i18n not defined and no messagebundle.properties + return false; + } + } + return createI18nSection(i18n, i18nToSupportedLocales); + } + + // css: + // - from .library/appData/manifest/sap.ui5/library/css + // - from library.js/initLibrary/noLibraryCSS + function css() { + const cssElement = findChild(libraryAppData, "css"); + if ( cssElement != null ) { + const css = cssElement._; + if ( css === "false" ) { + log.verbose(` sap.ui5/library/css property taken from .library appData: 'false'`); + return false; + } + } else if ( libraryJSInfo.noLibraryCSS ) { + log.verbose(` sap.ui5/library/css property extracted from library.js: 'false'`); + return false; + } + } + + // content + // - from library.js/initLibrary/ (types|elements|controls|interfaces) + function content() { + const libraryJS = libraryJSInfo; + if ( libraryJS.controls || libraryJS.elements || libraryJS.interfaces || libraryJS.types ) { + return { + controls: libraryJS.controls, + elements: libraryJS.elements, + types: libraryJS.types, + interfaces: libraryJS.interfaces + }; + } + } + + return { + i18n: i18n(), + css: css(), + content: content() + }; + } + + const sapUI5 = { + _version: sectionVersion(APP_DESCRIPTOR_V3_OTHER_SECTIONS), + dependencies: dependencies(), + contentDensities: contentDensities(), + library: createLibraryMetadata() + }; + + return sapUI5; + } + + /** + * Creates an i18n section: + * - either using bundleUrl and supportedLocales + * - or the i18n String + * + * @param {string} i18n bundle url, e.g. "messagebundle.properties" + * @param {Map>} i18nToSupportedLocales cache to determine the supportedLocales only once + * @returns {{bundleUrl: string, supportedLocales: string[]}|null|string} json structure with bundleUrl and + * supportedLocales or the i18n String if not a ".properties" file. + * null if given i18n String is null + */ + function createI18nSection(i18n, i18nToSupportedLocales) { + if (i18n === undefined) { + return undefined; + } + if (!i18n.endsWith(".properties")) { + return i18n; + } + + // if the supported locales information should not be included use i18n text + if (!includeSupportedLocalesInformation) { + return i18n; + } + + let supportedLocales = i18nToSupportedLocales.get(i18n); + + if (!supportedLocales) { + supportedLocales = new Set(); + + if (libBundle.findResource(i18n) != null) { + supportedLocales.add(""); + } + const i18nPathPrefix = i18n.substring(0, i18n.length - ".properties".length) + "_"; + // e.g. i18n/i18n_ + + libBundle.getResources().forEach((resource) => { + const resPath = resource.getPath(); + // e.g. sap/ui/mine/i18n/i18n_en.properties + const indexOfI18nPathPrefix = resPath.lastIndexOf(i18nPathPrefix); + if (resPath.endsWith(".properties") && indexOfI18nPathPrefix >= 0) { + const i18nPath = resPath.substring(indexOfI18nPathPrefix + i18nPathPrefix.length, + resPath.length - ".properties".length); + if (!i18nPath.includes(".")) { + supportedLocales.add(i18nPath.replace(/_/g, "-")); + } + } + }); + i18nToSupportedLocales.set(i18n, supportedLocales); + } + + const supportedLocalesArray = Array.from(supportedLocales); + supportedLocalesArray.sort(); + + return { + bundleUrl: i18n, + supportedLocales: supportedLocalesArray, + fallbackLocale: supportedLocalesArray.length === 1 ? supportedLocalesArray[0] : undefined + }; + } + + function createSapFiori() { + // collect registrationIds if present + function registrationIds() { + const ids = []; + for (const regid of findChildren(sapFioriAppData, "registrationId")) { + ids.push(regid._); + } + return ids.length > 0 ? ids : undefined; + } + + if ( sapFioriAppData != null ) { + return { + _version: sectionVersion(APP_DESCRIPTOR_V3_OTHER_SECTIONS), + registrationIds: registrationIds(), + archeType: getChildTextContent(sapFioriAppData, "archeType", "reuseComponent") + }; + } + } + + function createSapPlatformABAP() { + const sapPlatformABAPElement = findChild(manifestAppData, "sap.platform.abap"); + if ( sapPlatformABAPElement ) { + return { + _version: sectionVersion(APP_DESCRIPTOR_V3_OTHER_SECTIONS), + uri: getChildTextContent(sapPlatformABAPElement, "uri") + }; + } + } + + function createSapPlatformHCP() { + const sapPlatformHCPElement = findChild(manifestAppData, "sap.platform.hcp"); + if ( sapPlatformHCPElement ) { + return { + _version: sectionVersion(APP_DESCRIPTOR_V3_OTHER_SECTIONS), + uri: getChildTextContent(sapPlatformHCPElement, "uri") + }; + } + } + + function normalizeVersion(version) { + if ( version == null ) { + return version; + } + const v = new Version(version); + return v.major + "." + v.minor; + } + + return { + "_version": descriptorVersion.toString(), + "sap.app": createSapApp(), + "sap.ui": createSapUi(), + "sap.ui5": createSapUI5(), + "sap.fiori": createSapFiori(), + "sap.platform.abap": createSapPlatformABAP(), + "sap.platform.hcp": createSapPlatformHCP() + }; +} + +export default function({libraryResource, resources, getProjectVersion, options}) { + // merge options with defaults + options = Object.assign({ + descriptorVersion: APP_DESCRIPTOR_V22.toString(), + include3rdParty: true, + prettyPrint: true, + omitMinVersions: false + }, options); + + const resourcePathPrefix = libraryResource.getPath().slice(0, -".library".length); + const libBundle = new LibraryBundle(resourcePathPrefix, resources); + + // check whether a manifest exists already + const manifestResource = libBundle.findResource("manifest.json"); + if ( manifestResource != null ) { + log.verbose(`Library manifest already exists at '${manifestResource.getPath()}', skipping generation`); + return Promise.resolve(null); // a fulfillment of null indicates that no manifest has been created + } + + return createManifest(libraryResource, libBundle, new Version(options.descriptorVersion), options.include3rdParty, + options.omitMinVersions, getProjectVersion) + .then((manifest) => { + return new Resource({ + path: resourcePathPrefix + "manifest.json", + string: JSON.stringify(manifest, null, options.prettyPrint ? " " : undefined) + }); + }); +} diff --git a/packages/builder/lib/processors/manifestEnhancer.js b/packages/builder/lib/processors/manifestEnhancer.js new file mode 100644 index 00000000000..d8b2135fcb8 --- /dev/null +++ b/packages/builder/lib/processors/manifestEnhancer.js @@ -0,0 +1,491 @@ +import semver from "semver"; +const {SemVer: Version, lt} = semver; +import {promisify} from "node:util"; +import path from "node:path/posix"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:manifestEnhancer"); + +const APP_DESCRIPTOR_V22 = new Version("1.21.0"); + +/* + * Matches a legacy Java locale string, which is the format used by the UI5 Runtime (ResourceBundle) + * to load i18n properties files. + * Special case: "sr_Latn" is also supported, although the BCP47 script part is not supported by the Java locale format. + * + * Variants are limited to the format from BCP47, but with underscores instead of hyphens. + */ +// [ language ] [ region ][ variants ] +const rLegacyJavaLocale = /^([a-z]{2,3}|sr_Latn)(?:_([A-Z]{2}|\d{3})((?:_[0-9a-zA-Z]{5,8}|_[0-9][0-9a-zA-Z]{3})*)?)?$/; + +// See https://github.com/SAP/openui5/blob/d7ecf2792788719d35b4eee3085a327d545bab24/src/sap.ui.core/src/sap/base/i18n/LanguageFallback.js#L10 +const sapSupportabilityVariants = ["saptrc", "sappsd", "saprigi"]; + +function getBCP47LocaleFromPropertiesFilename(locale) { + const match = rLegacyJavaLocale.exec(locale); + if (!match) { + return null; + } + let [, language, region, variants] = match; + let script; + + variants = variants?.slice(1); // Remove leading underscore + + // Special handling of sr_Latn (see regex above) + // Note: This needs to be in sync with the runtime logic: + // https://github.com/SAP/openui5/blob/d7ecf2792788719d35b4eee3085a327d545bab24/src/sap.ui.core/src/sap/base/i18n/LanguageFallback.js#L87 + if (language === "sr_Latn") { + language = "sr"; + script = "Latn"; + } + + if (language === "en" && region === "US" && sapSupportabilityVariants.includes(variants)) { + // Convert to private use section + // Note: This needs to be in sync with the runtime logic: + // https://github.com/SAP/openui5/blob/d7ecf2792788719d35b4eee3085a327d545bab24/src/sap.ui.core/src/sap/base/i18n/LanguageFallback.js#L75 + variants = `x-${variants}`; + } + + let bcp47Locale = language; + if (script) { + bcp47Locale += `-${script}`; + } + if (region) { + bcp47Locale += `-${region}`; + } + if (variants) { + // Convert to BCP47 variant format + bcp47Locale += `-${variants.replace(/_/g, "-")}`; + } + return bcp47Locale; +} + +function isAbsoluteUrl(url) { + if (url.startsWith("/")) { + return true; + } + try { + const parsedUrl = new URL(url); + // URL with ui5 protocol shouldn't be treated as absolute URL and will be handled separately + return parsedUrl.protocol !== "ui5:"; + } catch { + // URL constructor without base requires absolute URL and throws an error for relative URLs + return false; + } +} + +/** + * Returns a bundle URL from the given bundle name, relative to the given namespace. + * + * @param {string} bundleName Bundle name (e.g. "sap.ui.demo.app.i18n.i18n") to be resolved to a relative URL + * @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app") + * to which a bundleName should be resolved to + * @returns {string} Relative bundle URL (e.g. "i18n/i18n.properties") + */ +function getRelativeBundleUrlFromName(bundleName, sapAppId) { + const bundleUrl = "/resources/" + bundleName.replace(/\./g, "/") + ".properties"; + return normalizeBundleUrl(bundleUrl, sapAppId); +} + +// Copied from sap/base/util/LoaderExtensions.resolveUI5Url +// Adjusted to not resolve the URL, but create an absolute path prefixed with /resources +function resolveUI5Url(sUrl) { + // check for ui5 scheme + if (sUrl.startsWith("ui5:")) { + let sNoScheme = sUrl.replace("ui5:", ""); + + // check for authority + if (!sNoScheme.startsWith("//")) { + // URLs using the 'ui5' protocol must be absolute. + // Relative and server absolute URLs are reserved for future use. + return null; + } + + sNoScheme = sNoScheme.replace("//", ""); + + return "/resources/" + sNoScheme; + } else { + // not a ui5 url + return sUrl; + } +} + +/** + * Normalizes a bundle URL relative to the project namespace. + * + * @param {string} bundleUrl Relative bundle URL to be normalized + * @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app") + * to which the URL is relative to + * @returns {string} Normalized relative bundle URL (e.g. "i18n/i18n.properties") + */ +function normalizeBundleUrl(bundleUrl, sapAppId) { + // Create absolute path with namespace from sap.app/id + const absoluteNamespace = `/resources/${sapAppId.replaceAll(/\./g, "/")}`; + + const resolvedAbsolutePath = path.resolve(absoluteNamespace, bundleUrl); + const resolvedRelativePath = path.relative(absoluteNamespace, resolvedAbsolutePath); + return resolvedRelativePath; +} + +/** + * Returns the bundle URL from the given bundle configuration. + * + * @param {object} bundleConfig Bundle configuration + * @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app") + * to which a bundleName should be resolved to + * @param {string} [defaultBundleUrl] Default bundle url in case bundleConfig is not defined + */ +function getBundleUrlFromConfig(bundleConfig, sapAppId, defaultBundleUrl) { + if (!bundleConfig) { + // Use default URL (or undefined if argument is not provided) + return defaultBundleUrl; + } else if (typeof bundleConfig === "string") { + return bundleConfig; + } else if (typeof bundleConfig === "object") { + return getBundleUrlFromConfigObject(bundleConfig, sapAppId); + } +} + +// Same as above, but does only accept objects, not strings or defaults +function getBundleUrlFromConfigObject(bundleConfig, sapAppId, fallbackBundleUrl) { + if (typeof bundleConfig === "object") { + if (bundleConfig.bundleName) { + return getRelativeBundleUrlFromName(bundleConfig.bundleName, sapAppId); + } else if (bundleConfig.bundleUrl) { + return bundleConfig.bundleUrl; + } + } + return fallbackBundleUrl; +} + +// See runtime logic in sap/ui/core/Lib#_normalizeI18nSettings +function getBundleUrlFromSapUi5LibraryI18n(vI18n) { + if (vI18n == null || vI18n === true) { + return "messagebundle.properties"; + } else if (typeof vI18n === "string") { + return vI18n; + } else if (typeof vI18n === "object") { + return vI18n.bundleUrl; + } else { + return null; + } +} + +class ManifestEnhancer { + /** + * @param {string} manifest manifest.json content + * @param {string} filePath manifest.json file path + * @param {fs} fs Node fs or custom [fs interface]{@link module:@ui5/fs/fsInterface} + */ + constructor(manifest, filePath, fs) { + this.fsReadDir = promisify(fs.readdir); + this.cwd = path.dirname(filePath); + this.filePath = filePath; + this.manifest = JSON.parse(manifest); + + this.isModified = false; + this.runInvoked = false; + + this.supportedLocalesCache = new Map(); + } + + markModified() { + this.isModified = true; + } + + async readdir(relativePath) { + const absolutePath = path.resolve(this.cwd, relativePath); + try { + return await this.fsReadDir(absolutePath); + } catch (err) { + if (err?.code === "ENOENT") { + return []; + } else { + throw err; + } + } + } + + findSupportedLocales(i18nBundleUrl) { + if (!this.supportedLocalesCache.has(i18nBundleUrl)) { + this.supportedLocalesCache.set(i18nBundleUrl, this._findSupportedLocales(i18nBundleUrl)); + } + return this.supportedLocalesCache.get(i18nBundleUrl); + } + + async _findSupportedLocales(i18nBundleUrl) { + const i18nBundleName = path.basename(i18nBundleUrl, ".properties"); + const i18nBundlePrefix = `${i18nBundleName}_`; + const i18nBundleDir = path.dirname(i18nBundleUrl); + const i18nBundleFiles = await this.readdir(i18nBundleDir); + const supportedLocales = []; + i18nBundleFiles.forEach((fileName) => { + if (!fileName.endsWith(".properties")) { + return; + } + const fileNameWithoutExtension = path.basename(fileName, ".properties"); + if (fileNameWithoutExtension === i18nBundleName) { + supportedLocales.push(""); + } else if (fileNameWithoutExtension.startsWith(i18nBundlePrefix)) { + const fileNameLocale = fileNameWithoutExtension.replace(i18nBundlePrefix, ""); + const bcp47Locale = getBCP47LocaleFromPropertiesFilename(fileNameLocale); + if (bcp47Locale) { + supportedLocales.push(bcp47Locale); + } else { + log.warn(`Ignoring unexpected locale in filename '${fileName}' for bundle '${i18nBundleUrl}'`); + } + } + }); + return supportedLocales.sort(); + } + + async processBundleConfig({bundleConfig, fallbackBundleUrl, isTerminologyBundle = false, fallbackLocale}) { + const bundleUrl = getBundleUrlFromConfigObject(bundleConfig, this.manifest["sap.app"].id, fallbackBundleUrl); + if (!bundleUrl) { + return; + } + if (bundleConfig.supportedLocales) { + return; + } + + const supportedLocales = await this.getSupportedLocales( + bundleUrl, fallbackLocale ?? bundleConfig.fallbackLocale, isTerminologyBundle + ); + if (supportedLocales.length > 0) { + bundleConfig.supportedLocales = supportedLocales; + this.markModified(); + } + } + + async getSupportedLocales(bundleUrl, fallbackLocale, isTerminologyBundle = false) { + // Ignore absolute URLs + if (isAbsoluteUrl(bundleUrl)) { + return []; + } + const resolvedBundleUrl = resolveUI5Url(bundleUrl); + if (!resolvedBundleUrl) { + // In case of a relative ui5-protocol URL + return []; + } + const sapAppId = this.manifest["sap.app"].id; + const normalizedBundleUrl = normalizeBundleUrl(resolvedBundleUrl, sapAppId); + if (normalizedBundleUrl.startsWith("../")) { + log.verbose( + `${this.filePath}: ` + + `bundleUrl '${bundleUrl}' points to a bundle outside of the ` + + `current namespace '${sapAppId}', enhancement of 'supportedLocales' is skipped` + ); + return []; + } + const supportedLocales = await this.findSupportedLocales(normalizedBundleUrl); + if (!isTerminologyBundle && supportedLocales.length > 0) { + if (typeof fallbackLocale === "string" && !supportedLocales.includes(fallbackLocale)) { + log.error( + `${this.filePath}: ` + + `Generated supported locales ('${supportedLocales.join("', '")}') for ` + + `bundle '${normalizedBundleUrl}' ` + + "not containing the defined fallback locale '" + fallbackLocale + "'. Either provide a " + + "properties file for defined fallbackLocale or configure another available fallbackLocale" + ); + return []; + } else if (typeof fallbackLocale === "undefined" && !supportedLocales.includes("en")) { + log.warn( + `${this.filePath}: ` + + `Generated supported locales ('${supportedLocales.join("', '")}') for ` + + `bundle '${normalizedBundleUrl}' ` + + "do not contain default fallback locale 'en'. Either provide a " + + "properties file for 'en' or configure another available fallbackLocale" + ); + } + } + return supportedLocales; + } + + async processSapAppI18n() { + const sapApp = this.manifest["sap.app"]; + let sapAppI18n = sapApp.i18n; + + // Process enhanceWith bundles first, as they check for an existing supportedLocales property + // defined by the developer, but not the one generated by the tooling. + await this.processTerminologiesAndEnhanceWith(sapAppI18n); + + const i18nBundleUrl = getBundleUrlFromConfig(sapAppI18n, sapApp.id, "i18n/i18n.properties"); + + if (!sapAppI18n?.supportedLocales && i18nBundleUrl) { + const supportedLocales = await this.getSupportedLocales(i18nBundleUrl, sapAppI18n?.fallbackLocale); + if (supportedLocales.length > 0) { + if (!sapAppI18n || typeof sapAppI18n === "string") { + sapAppI18n = sapApp.i18n = { + bundleUrl: i18nBundleUrl + }; + } + sapAppI18n.supportedLocales = supportedLocales; + this.markModified(); + } + } + } + + /** + * Processes the terminologies and enhanceWith bundles of a bundle configuration. + * + * @param {object} bundleConfig + */ + async processTerminologiesAndEnhanceWith(bundleConfig) { + const bundleConfigs = []; + const terminologyBundleConfigs = []; + + if (bundleConfig?.terminologies) { + terminologyBundleConfigs.push(...Object.values(bundleConfig.terminologies)); + } + + bundleConfig?.enhanceWith?.forEach((config) => { + // The runtime logic propagates supportedLocales information to the enhanceWith bundles. + // In order to not break existing behavior, we do not generate supportedLocales for enhanceWith bundles + // in case the parent bundle does have supportedLocales defined. + if (!bundleConfig.supportedLocales) { + bundleConfigs.push({config, fallbackLocale: bundleConfig.fallbackLocale}); + } + if (config.terminologies) { + terminologyBundleConfigs.push(...Object.values(config.terminologies)); + } + }); + + await Promise.all( + bundleConfigs.map(({config, fallbackLocale}) => this.processBundleConfig({ + bundleConfig: config, + fallbackLocale + })) + ); + await Promise.all( + terminologyBundleConfigs.map((bundleConfig) => this.processBundleConfig({ + bundleConfig, isTerminologyBundle: true + })) + ); + } + + async processSapUi5Models() { + const sapUi5Models = this.manifest["sap.ui5"]?.models; + if (typeof sapUi5Models !== "object") { + return; + } + const modelConfigs = Object.values(sapUi5Models) + .filter((modelConfig) => modelConfig.type === "sap.ui.model.resource.ResourceModel"); + + await Promise.all( + modelConfigs.map(async (modelConfig) => { + // Process enhanceWith bundles first, as they check for an existing supportedLocales property + // defined by the developer, but not the one generated by the tooling. + await this.processTerminologiesAndEnhanceWith(modelConfig.settings); + + // Fallback to empty settings object in case only a "uri" is defined which will be converted + // to a settings object at runtime. + const settings = modelConfig.settings || {}; + + // Ensure to pass the "uri" property as fallback bundle URL according to the runtime logic. + // It is only taken into account if no "bundleUrl" or "bundleName" is defined. + await this.processBundleConfig({bundleConfig: settings, fallbackBundleUrl: modelConfig.uri}); + + // Ensure that the settings object is assigned back to the modelConfig + // in case it didn't existing before. + if (!modelConfig.settings) { + modelConfig.settings = settings; + } + }) + ); + } + + async processSapUi5LibraryI18n() { + let sapUi5LibraryI18n = this.manifest["sap.ui5"]?.library?.i18n; + + // Process enhanceWith bundles first, as they check for an existing supportedLocales property + // defined by the developer, but not the one generated by the tooling. + await this.processTerminologiesAndEnhanceWith(sapUi5LibraryI18n); + + const i18nBundleUrl = getBundleUrlFromSapUi5LibraryI18n(sapUi5LibraryI18n); + if (i18nBundleUrl && !sapUi5LibraryI18n?.supportedLocales) { + const supportedLocales = await this.getSupportedLocales(i18nBundleUrl, sapUi5LibraryI18n?.fallbackLocale); + if (supportedLocales.length > 0) { + if (!sapUi5LibraryI18n || typeof sapUi5LibraryI18n !== "object") { + this.manifest["sap.ui5"] ??= {}; + this.manifest["sap.ui5"].library ??= {}; + sapUi5LibraryI18n = this.manifest["sap.ui5"].library.i18n = { + bundleUrl: i18nBundleUrl + }; + } + sapUi5LibraryI18n.supportedLocales = supportedLocales; + this.markModified(); + } + } + } + + async run() { + // Prevent multiple invocations + if (this.runInvoked) { + throw new Error("ManifestEnhancer#run can only be invoked once per instance"); + } + this.runInvoked = true; + + if (!this.manifest._version) { + log.verbose(`${this.filePath}: _version is not defined. No supportedLocales can be generated`); + return; + } + + if (lt(this.manifest._version, APP_DESCRIPTOR_V22)) { + log.verbose(`${this.filePath}: _version is lower than 1.21.0 so no supportedLocales can be generated`); + return; + } + + if (!this.manifest["sap.app"]?.id) { + log.verbose(`${this.filePath}: sap.app/id is not defined. No supportedLocales can be generated`); + return; + } + + if (this.manifest["sap.app"].type === "library") { + await this.processSapUi5LibraryI18n(); + } else { + await Promise.all([ + this.processSapAppI18n(), + this.processSapUi5Models() + ]); + } + + if (this.isModified) { + return this.manifest; + } + } +} + +/** + * @module @ui5/builder/processors/manifestEnhancer + */ + +/** + * Enriches the content of the manifest.json file. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of manifest.json resources to be processed + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom + * [fs interface]{@link module:@ui5/fs/fsInterface}. + * @returns {Promise>} Promise resolving with an array of modified resources + */ +export default async function({resources, fs}) { + const res = await Promise.all( + resources.map(async (resource) => { + const manifest = await resource.getString(); + const filePath = resource.getPath(); + const manifestEnhancer = new ManifestEnhancer(manifest, filePath, fs); + const enrichedManifest = await manifestEnhancer.run(); + if (enrichedManifest) { + resource.setString(JSON.stringify(enrichedManifest, null, 2)); + return resource; + } + }) + ); + return res.filter(($) => $); +} + +export const __internals__ = (process.env.NODE_ENV === "test") ? + {ManifestEnhancer, getRelativeBundleUrlFromName, normalizeBundleUrl, resolveUI5Url} : undefined; diff --git a/packages/builder/lib/processors/minifier.js b/packages/builder/lib/processors/minifier.js new file mode 100644 index 00000000000..97242b5f04a --- /dev/null +++ b/packages/builder/lib/processors/minifier.js @@ -0,0 +1,290 @@ +import {fileURLToPath} from "node:url"; +import posixPath from "node:path/posix"; +import {promisify} from "node:util"; +import os from "node:os"; +import workerpool from "workerpool"; +import Resource from "@ui5/fs/Resource"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:minifier"); +import {setTimeout as setTimeoutPromise} from "node:timers/promises"; + +const debugFileRegex = /((?:\.view|\.fragment|\.controller|\.designtime|\.support)?\.js)$/; + +const MIN_WORKERS = 2; +const MAX_WORKERS = 4; +const osCpus = os.cpus().length || 1; +const maxWorkers = Math.max(Math.min(osCpus - 1, MAX_WORKERS), MIN_WORKERS); + +const sourceMappingUrlPattern = /\/\/# sourceMappingURL=(\S+)\s*$/; +const httpPattern = /^https?:\/\//i; + +// Shared workerpool across all executions until the taskUtil cleanup is triggered +let pool; + +function getPool(taskUtil) { + if (!pool) { + log.verbose(`Creating workerpool with up to ${maxWorkers} workers (available CPU cores: ${osCpus})`); + const workerPath = fileURLToPath(new URL("./minifierWorker.js", import.meta.url)); + pool = workerpool.pool(workerPath, { + workerType: "auto", + maxWorkers + }); + taskUtil.registerCleanupTask((force) => { + const attemptPoolTermination = async () => { + log.verbose(`Attempt to terminate the workerpool...`); + + if (!pool) { + return; + } + + // There are many stats that could be used, but these ones seem the most + // convenient. When all the (available) workers are idle, then it's safe to terminate. + let {idleWorkers, totalWorkers} = pool.stats(); + while (idleWorkers !== totalWorkers && !force) { + await setTimeoutPromise(100); // Wait a bit workers to finish and try again + + if (!pool) { // pool might have been terminated in the meantime + return; + } + ({idleWorkers, totalWorkers} = pool.stats()); + } + + const poolToBeTerminated = pool; + pool = null; + return poolToBeTerminated.terminate(force); + }; + + return attemptPoolTermination(); + }); + } + return pool; +} + +async function minifyInWorker(options, taskUtil) { + return getPool(taskUtil).exec("execMinification", [options]); +} + +async function extractAndRemoveSourceMappingUrl(resource) { + const resourceContent = await resource.getString(); + const resourcePath = resource.getPath(); + const sourceMappingUrlMatch = resourceContent.match(sourceMappingUrlPattern); + if (sourceMappingUrlMatch) { + const sourceMappingUrl = sourceMappingUrlMatch[1]; + if (log.isLevelEnabled("silly")) { + log.silly(`Found source map reference in content of resource ${resourcePath}: ${sourceMappingUrl}`); + } + + // Strip sourceMappingURL from the resource to be minified + // It is not required anymore and will be replaced for in the minified resource + // and its debug variant anyways + resource.setString(resourceContent.replace(sourceMappingUrlPattern, "")); + return sourceMappingUrl; + } + return null; +} + +async function getSourceMapFromUrl({sourceMappingUrl, resourcePath, readFile}) { + // ======================================================================= + // This code is almost identical to code located in lbt/bundle/Builder.js + // Please try to update both places when making changes + // ======================================================================= + if (sourceMappingUrl.startsWith("data:")) { + // Data-URI indicates an inline source map + const expectedTypeAndEncoding = "data:application/json;charset=utf-8;base64,"; + if (sourceMappingUrl.startsWith(expectedTypeAndEncoding)) { + const base64Content = sourceMappingUrl.slice(expectedTypeAndEncoding.length); + // Create a resource with a path suggesting it's the source map for the resource + // (which it is but inlined) + return Buffer.from(base64Content, "base64").toString(); + } else { + log.warn( + `Source map reference in resource ${resourcePath} is a data URI but has an unexpected` + + `encoding: ${sourceMappingUrl}. Expected it to start with ` + + `"data:application/json;charset=utf-8;base64,"`); + } + } else if (httpPattern.test(sourceMappingUrl)) { + log.warn(`Source map reference in resource ${resourcePath} is an absolute URL. ` + + `Currently, only relative URLs are supported.`); + } else if (posixPath.isAbsolute(sourceMappingUrl)) { + log.warn(`Source map reference in resource ${resourcePath} is an absolute path. ` + + `Currently, only relative paths are supported.`); + } else { + const sourceMapPath = posixPath.join(posixPath.dirname(resourcePath), sourceMappingUrl); + + try { + const sourceMapContent = await readFile(sourceMapPath); + return sourceMapContent.toString(); + } catch (e) { + // No input source map + log.warn(`Unable to read source map for resource ${resourcePath}: ${e.message}`); + } + } +} + +/** + * @public + * @module @ui5/builder/processors/minifier + */ + +/** + * Result set + * + * @public + * @typedef {object} MinifierResult + * @property {@ui5/fs/Resource} resource Minified resource + * @property {@ui5/fs/Resource} dbgResource Debug (non-minified) variant + * @property {@ui5/fs/Resource} sourceMap Source Map + */ + +/** + * Minifies the supplied resources. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom + * [fs interface]{@link module:@ui5/fs/fsInterface}. Required when setting "readSourceMappingUrl" to true + * @param {@ui5/builder/tasks/TaskUtil|object} [parameters.taskUtil] TaskUtil instance. + * Required when using the useWorkers option + * @param {object} [parameters.options] Options + * @param {boolean} [parameters.options.readSourceMappingUrl=false] + * Whether to make use of any existing source maps referenced in the resources to be minified. Use this option to + * preserve references to the original source files, such as TypeScript files, in the generated source map.
    + * If a resource has been modified by a previous task, any existing source map will be ignored regardless of this + * setting. This is to ensure that no inconsistent source maps are used. Check the verbose log for details. + * @param {boolean} [parameters.options.addSourceMappingUrl=true] + * Whether to add a sourceMappingURL reference to the end of the minified resource + * @param {boolean} [parameters.options.useWorkers=false] + * Whether to offload the minification task onto separate CPU threads. This often speeds up the build process + * @returns {Promise} + * Promise resolving with object of resource, dbgResource and sourceMap + */ +export default async function({ + resources, fs, taskUtil, options: {readSourceMappingUrl = false, addSourceMappingUrl = true, useWorkers = false + } = {}}) { + let minify; + if (readSourceMappingUrl && !fs) { + throw new Error(`Option 'readSourceMappingUrl' requires parameter 'fs' to be provided`); + } + + if (useWorkers) { + if (!taskUtil) { + // TaskUtil is required for worker support + throw new Error(`Minifier: Option 'useWorkers' requires a taskUtil instance to be provided`); + } + minify = minifyInWorker; + } else { + // Do not use workerpool + minify = (await import("./minifierWorker.js")).default; + } + + return Promise.all(resources.map(async (resource) => { + const resourcePath = resource.getPath(); + const dbgPath = resourcePath.replace(debugFileRegex, "-dbg$1"); + const dbgFilename = posixPath.basename(dbgPath); + + const filename = posixPath.basename(resource.getPath()); + + const sourceMapOptions = { + filename + }; + if (addSourceMappingUrl) { + sourceMapOptions.url = filename + ".map"; + } + + // Remember contentModified flag before making changes to the resource via setString + const resourceContentModified = resource.getSourceMetadata()?.contentModified; + + // In any case: Extract *and remove* source map reference from resource before cloning it + const sourceMappingUrl = await extractAndRemoveSourceMappingUrl(resource); + + const code = await resource.getString(); + // Create debug variant based off the original resource before minification + const dbgResource = await resource.clone(); + dbgResource.setPath(dbgPath); + + let dbgSourceMapResource; + if (sourceMappingUrl) { + if (resourceContentModified) { + log.verbose( + `Source map found in resource will be ignored because the resource has been ` + + `modified in a previous task: ${resourcePath}`); + } else if (readSourceMappingUrl) { + // Try to find a source map reference in the to-be-minified resource + // If we find one, provide it to terser as an input source map and keep using it for the + // debug variant of the resource + const sourceMapContent = await getSourceMapFromUrl({ + sourceMappingUrl, + resourcePath, + readFile: promisify(fs.readFile) + }); + + if (sourceMapContent) { + const sourceMapJson = JSON.parse(sourceMapContent); + + if (sourceMapJson.sections) { + // TODO 5.0 + // Module "@jridgewell/trace-mapping" (used by Terser) can't handle index map sections lacking + // a "names" array. Since this is a common occurrence for UI5 CLI bundles, we search for + // such cases here and fix them until https://github.com/jridgewell/trace-mapping/pull/29 is + // resolved and Terser upgraded the dependency + + // Create a dedicated clone before modifying the source map as to not alter the debug source map + const clonedSourceMapJson = JSON.parse(sourceMapContent); + clonedSourceMapJson.sections.forEach(({map}) => { + if (!map.names) { + // Add missing names array + map.names = []; + } + }); + // Use modified source map as input source map + sourceMapOptions.content = JSON.stringify(clonedSourceMapJson); + } else { + // Provide source map to terser as "input source map" + sourceMapOptions.content = sourceMapContent; + } + + // Use the original source map for the debug variant of the resource + // First update the file reference within the source map + sourceMapJson.file = dbgFilename; + + // Then create a new resource + dbgSourceMapResource = new Resource({ + string: JSON.stringify(sourceMapJson), + path: dbgPath + ".map" + }); + // And reference the resource in the debug resource + dbgResource.setString(code + `//# sourceMappingURL=${dbgFilename}.map\n`); + } + } else { + // If the original resource content was unmodified and the input source map was not parsed, + // re-add the original source map reference to the debug variant + if (!sourceMappingUrl.startsWith("data:") && !sourceMappingUrl.endsWith(filename + ".map")) { + // Do not re-add inline source maps as well as references to the source map of + // the minified resource + dbgResource.setString(code + `//# sourceMappingURL=${sourceMappingUrl}\n`); + } + } + } + + const result = await minify({ + filename, + dbgFilename, + code, + resourcePath, + sourceMapOptions + }, taskUtil); + resource.setString(result.code); + const sourceMapResource = new Resource({ + path: resource.getPath() + ".map", + string: result.map + }); + return {resource, dbgResource, sourceMapResource, dbgSourceMapResource}; + })); +} + +export const __localFunctions__ = (process.env.NODE_ENV === "test") ? + {getSourceMapFromUrl} : undefined; diff --git a/packages/builder/lib/processors/minifierWorker.js b/packages/builder/lib/processors/minifierWorker.js new file mode 100644 index 00000000000..a54e75f946a --- /dev/null +++ b/packages/builder/lib/processors/minifierWorker.js @@ -0,0 +1,81 @@ +import workerpool from "workerpool"; +import {minify} from "terser"; + +/** + * @private + * @module @ui5/builder/tasks/minifyWorker + */ + +/** + * Preserve comments which contain: + *
      + *
    • copyright notice
    • + *
    • license terms
    • + *
    • "@ui5-bundle"
    • + *
    • "@ui5-bundle-raw-include"
    • + *
    + * + * @type {RegExp} + */ +const copyrightCommentsAndBundleCommentPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-Za-z])|released under|license|\u00a9|^@ui5-bundle-raw-include |^@ui5-bundle /i; + +/** + * Task to minify resources. + * + * @private + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {string} parameters.filename + * @param {string} parameters.dbgFilename + * @param {string} parameters.code + * @param {string} parameters.resourcePath + * @param {object} parameters.sourceMapOptions + * @returns {Promise} Promise resolving once minification of the resource has finished + */ +export default async function execMinification({ + filename, + dbgFilename, + code, + resourcePath, + sourceMapOptions +}) { + try { + return await minify({ + // Use debug-name since this will be referenced in the source map "sources" + [dbgFilename]: code + }, { + output: { + comments: copyrightCommentsAndBundleCommentPattern, + wrap_func_args: false + }, + compress: false, + mangle: { + reserved: [ + "jQuery", + "jquery", + "sap", + ] + }, + sourceMap: sourceMapOptions + }); + } catch (err) { + // Note: err.filename contains the debug-name + throw new Error( + `Minification failed with error: ${err.message} in file ${resourcePath} ` + + `(line ${err.line}, col ${err.col}, pos ${err.pos})`, { + cause: err + }); + } +} + +// Test execution via ava is never done on the main thread +/* istanbul ignore else */ +if (!workerpool.isMainThread) { + // Script got loaded through workerpool + // => Create a worker and register public functions + workerpool.worker({ + execMinification + }); +} diff --git a/packages/builder/lib/processors/nonAsciiEscaper.js b/packages/builder/lib/processors/nonAsciiEscaper.js new file mode 100644 index 00000000000..858eac7745d --- /dev/null +++ b/packages/builder/lib/processors/nonAsciiEscaper.js @@ -0,0 +1,119 @@ +import escapeUnicode from "escape-unicode"; + +/** + * @public + * @module @ui5/builder/processors/nonAsciiEscaper + */ + +/** + * @see https://en.wikipedia.org/wiki/ASCII + * ascii contains 128 characters. + * its char codes reach from 0 to 127. + * @type {number} + */ +const CHAR_CODE_OF_LAST_ASCII_CHARACTER = 127; + +// use memoization for escapeUnicode function for performance +const memoizeEscapeUnicodeMap = Object.create(null); +const memoizeEscapeUnicode = function(sChar) { + if (memoizeEscapeUnicodeMap[sChar]) { + return memoizeEscapeUnicodeMap[sChar]; + } + memoizeEscapeUnicodeMap[sChar] = escapeUnicode(sChar); + return memoizeEscapeUnicodeMap[sChar]; +}; + +/** + * Escapes non ASCII characters with unicode escape sequences. + * + * @see https://en.wikipedia.org/wiki/ASCII + * @see https://tools.ietf.org/html/rfc5137#section-6.1 + * + * + * @param {string} string input string with non ascii characters, e.g. L♥VE + * @returns {{string: (string), modified: boolean}} output string with all non ascii + * characters being escaped by unicode sequence, e.g. L\u2665VE + */ +const escapeNonAscii = function(string) { + let result = ""; + let modified = false; + for (let i = 0; i < string.length; i++) { + const char = string[i]; + // check for non ascii characters (characters which have a char code + // greater than the ascii character code range) + if (string.charCodeAt(i) > CHAR_CODE_OF_LAST_ASCII_CHARACTER) { + result += memoizeEscapeUnicode(char); + modified = true; + } else { + result += char; + } + } + return { + modified, + string: result + }; +}; + +/** + * Escapes non ASCII characters with unicode escape sequences. + * + * @example + * const encoding = nonAsciiEscaper.getEncodingFromAlias("ISO-8859-1"); + * nonAsciiEscaper({resources, options: {encoding}}); + * + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed + * @param {object} [parameters.options] Options + * @param {string} [parameters.options.encoding="utf8"] resource file encoding + * ({@link https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings Node.js character encodings}). + * Use #getEncodingFromAlias to get the encoding string + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with the processed resources + */ +async function nonAsciiEscaper({resources, options: {encoding}}) { + encoding = encoding || "utf8"; + + async function processResource(resource) { + const resourceString = (await resource.getBuffer()).toString(encoding); + const escaped = escapeNonAscii(resourceString); + // only modify the resource's string if it was changed + if (escaped.modified) { + resource.setString(escaped.string); + } + return resource; + } + + return Promise.all(resources.map(processResource)); +} + +const encodingMap = { + "UTF-8": "utf8", + "ISO-8859-1": "latin1", +}; + +/** + * Provides a mapping from user-friendly encoding name (alias) such as "UTF-8" and "ISO-8859-1" to node + * specific encoding name such as "utf8" or "latin1". Simplifies usage of nonAsciiEscaper encoding option + * such that it can be used standalone without the respective task (e.g. in Splitter, Bundler and related projects). + * + * @public + * @function getEncodingFromAlias + * @alias @ui5/builder/processors/nonAsciiEscaper․getEncodingFromAlias + * @static + * + * @param {string} encoding encoding labels: "UTF-8" and "ISO-8859-1" + * @returns {string} node.js character encoding string, e.g. utf8 and latin1 + */ +nonAsciiEscaper.getEncodingFromAlias = function(encoding) { + if (!encodingMap[encoding]) { + throw new Error( + `Encoding "${encoding}" is not supported. Only ${Object.keys(encodingMap).join(", ")} are allowed values` ); + } + return encodingMap[encoding]; +}; + +export default nonAsciiEscaper; diff --git a/packages/builder/lib/processors/resourceListCreator.js b/packages/builder/lib/processors/resourceListCreator.js new file mode 100644 index 00000000000..5d8eaf246a3 --- /dev/null +++ b/packages/builder/lib/processors/resourceListCreator.js @@ -0,0 +1,190 @@ + +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:resourceListCreator"); +import ResourceCollector from "../lbt/resources/ResourceCollector.js"; +import LocatorResourcePool from "../lbt/resources/LocatorResourcePool.js"; +import ResourceInfo from "../lbt/resources/ResourceInfo.js"; +import Resource from "@ui5/fs/Resource"; + + +/** + * List of resource patterns that describe all debug resources. + * + * @since 1.29.1 + */ +const DEFAULT_DEBUG_RESOURCES_FILTER = [ + "**/*-dbg.js", + "**/*-dbg.controller.js", + "**/*-dbg.designtime.js", + "**/*-dbg.support.js", + "**/*-dbg.view.js", + "**/*-dbg.fragment.js", + "**/*-dbg.css", + "**/*.js.map" +]; + +/** + * List of resource patterns that describe bundled resources. + * + * @since 1.29.1 + */ +const DEFAULT_BUNDLE_RESOURCES_FILTER = [ + "**/Component-preload.js", + "**/library-preload.js", + "**/library-preload-dbg.js", + "**/library-preload.json", + "**/library-h2-preload.js", + "**/designtime/library-preload.designtime.js", + "**/library-preload.support.js", + "**/library-all.js", + "**/library-all-dbg.js" +]; + +/** + * List of resource patterns that describe all designtime resources. + * + * @since 1.31.0 + */ +const DEFAULT_DESIGNTIME_RESOURCES_FILTER = [ + "**/designtime/*", + "**/*.designtime.js", + "**/*.control", + "**/*.interface", + "**/*.type", + "**/themes/*/*.less", + "**/library.templates.xml", + "**/library.dependencies.xml", + "**/library.dependencies.json" +]; + +/** + * List of resource patterns that describe all support (assistant) resources. + * + * @since 1.53.0 + */ +const DEFAULT_SUPPORT_RESOURCES_FILTER = [ + "**/*.support.js" +]; + +/** + * Creates and adds resources.json entry (itself) to the list. + * + * Retrieves the string content of the overall result and returns it. + * + * @param {ResourceInfoList} list resources list + * @param {string} prefix + * @returns {string} new content with resources.json entry + */ +function makeResourcesJSON(list, prefix) { + // having the file size entry part of the file is a bit like the chicken egg scenario + // you can't change the value of the file size without changing the file size + // so this part here tries to cope with that. + + // try to add resources.json entry with previous size of the list string. + // get the content to be added (resources.json entry) + // modify the size of the entry from the calculated one + + let contentString = JSON.stringify(list, null, "\t"); + const resourcesJson = new ResourceInfo(prefix + "resources.json"); + // initial size + resourcesJson.size = Buffer.from(contentString).byteLength; + list.add(resourcesJson); + + contentString = JSON.stringify(list, null, "\t"); + + let newLength = Buffer.from(contentString).byteLength; + + // Adjust size until it is correct + // This entry's size depends on the file size which depends on this entry's size,... + // Updating the number of the size in the content might influence the size of the file itself + // This is deterministic because e.g. in the content -> "size": 1000 has the same + // amount of bytes as "size": 9999 the difference might only come for: + // * adding the initial entry of resources.json + // * changes when the number of digits of the number changes, e.g. 100 -> 1000 + while (resourcesJson.size !== newLength) { + resourcesJson.size = newLength; + list.add(resourcesJson); + contentString = JSON.stringify(list, null, "\t"); + newLength = Buffer.from(contentString).byteLength; + } + return contentString; +} + +/** + * Creates resources.json files + * + * @private + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources + * @param {@ui5/fs/Resource[]} [parameters.dependencyResources=[]] List of dependency resources + * @param {object} [parameters.options] Options + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with the resources.json resources + */ +export default async function({resources, dependencyResources = [], options}) { + options = Object.assign({ + failOnOrphans: false, + externalResources: undefined, + debugResources: DEFAULT_DEBUG_RESOURCES_FILTER, + mergedResources: DEFAULT_BUNDLE_RESOURCES_FILTER, + designtimeResources: DEFAULT_DESIGNTIME_RESOURCES_FILTER, + supportResources: DEFAULT_SUPPORT_RESOURCES_FILTER + }, options); + + const pool = new LocatorResourcePool(); + await pool.prepare( resources ); + await pool.prepare( dependencyResources ); + + const collector = new ResourceCollector(pool); + const visitPromises = resources.map((resource) => collector.visitResource(resource)); + + await Promise.all(visitPromises); + log.verbose(` Found ${collector.resources.size} resources`); + + // determine additional information for the found resources + if ( options && options.externalResources ) { + collector.setExternalResources(options.externalResources); + } + + await collector.determineResourceDetails({ + debugResources: options.debugResources, + mergedResources: options.mergedResources, + designtimeResources: options.designtimeResources, + supportResources: options.supportResources + }); + + // group resources by components and create ResourceInfoLists + collector.groupResourcesByComponents(); + + const resourceLists = []; + + // write out resources.json files + for (const [prefix, list] of collector.components.entries()) { + log.verbose(` Writing '${prefix}resources.json'`); + + const contentString = makeResourcesJSON(list, prefix); + + resourceLists.push(new Resource({ + path: `/resources/${prefix}resources.json`, + string: contentString + })); + } + for (const [prefix, list] of collector.themePackages.entries()) { + log.verbose(` Writing '${prefix}resources.json'`); + + const contentString = makeResourcesJSON(list, prefix); + + resourceLists.push(new Resource({ + path: `/resources/${prefix}resources.json`, + string: contentString + })); + } + const unassigned = collector.resources; + if ( unassigned.size > 0 && options.failOnOrphans ) { + log.error(`resources.json generation failed because of unassigned resources: ${[...unassigned].join(", ")}`); + throw new Error( + `resources.json generation failed with error: There are ${unassigned.size} ` + + `resources which could not be assigned to components.`); + } + + return resourceLists; +} diff --git a/packages/builder/lib/processors/stringReplacer.js b/packages/builder/lib/processors/stringReplacer.js new file mode 100644 index 00000000000..2485032cc76 --- /dev/null +++ b/packages/builder/lib/processors/stringReplacer.js @@ -0,0 +1,29 @@ +/** + * @public + * @module @ui5/builder/processors/stringReplacer + */ + +/** + * Replaces placeholders with corresponding values. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Pattern of placeholders + * @param {string} parameters.options.replacement Replacement for placeholders + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with modified resources + */ +export default function({resources, options: {pattern, replacement}}) { + return Promise.all(resources.map(async (resource) => { + const content = await resource.getString(); + const newContent = content.replaceAll(pattern, replacement); + if (content !== newContent) { + resource.setString(newContent); + } + return resource; + })); +} diff --git a/packages/builder/lib/processors/themeBuilder.js b/packages/builder/lib/processors/themeBuilder.js new file mode 100644 index 00000000000..2a4a760b90c --- /dev/null +++ b/packages/builder/lib/processors/themeBuilder.js @@ -0,0 +1,169 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:themeBuilder"); +import posixPath from "node:path/posix"; +import less from "less-openui5"; +import Resource from "@ui5/fs/Resource"; + +const libraryMatchPattern = /^\/resources\/(.*)\/themes\/[^/]*\/library\.source\.less$/i; + +/** + * @public + * @module @ui5/builder/processors/ThemeBuilder + */ + +/** + * Builds a library theme + * + * @public + * @class + */ +export class ThemeBuilder { + /** + * Constructor + * + * @public + * @param {fs|module:@ui5/fs/fsInterface} fs Node fs or custom + * [fs interface]{@link module:@ui5/fs/fsInterface} + */ + constructor({fs}) { + this.builder = new less.Builder({fs}); + } + + /** + * Starts the theme build + * + * @public + * @param {@ui5/fs/Resource[]} resources Library files + * @param {object} [options] Build options + * @param {boolean} [options.compress=false] Compress build output (CSS / JSON) + * @param {boolean} [options.cssVariables=false] Generates the CSS variables + * (css-variables.css, css-variables.source.less) and the skeleton for a theme + * (library-skeleton.css, [library-skeleton-RTL.css]) + * @returns {Promise<@ui5/fs/Resource[]>} Resolving with array of created files + */ + build(resources, {compress = false, cssVariables = false} = {}) { + const files = []; + + const compile = (resource) => { + log.verbose(`Compiling ${resource.getPath()}`); + + let libraryName; + const libraryMatch = libraryMatchPattern.exec(resource.getPath()); + if (libraryMatch) { + libraryName = libraryMatch[1].replace(/\//g, "."); + } + + return this.builder.build({ + lessInputPath: resource.getPath(), + library: { + name: libraryName + }, + compiler: { + compress + }, + cssVariables + }).then((result) => { + const themeDir = posixPath.dirname(resource.getPath()); + + const libCss = new Resource({ + path: themeDir + "/library.css", + string: result.css + }); + + const libCssRtl = new Resource({ + path: themeDir + "/library-RTL.css", + string: result.cssRtl + }); + + const libParams = new Resource({ + path: themeDir + "/library-parameters.json", + string: JSON.stringify(result.variables, null, compress ? null : "\t") + }); + + files.push(libCss, libCssRtl, libParams); + + if (cssVariables) { + const libCssVarsSource = new Resource({ + path: themeDir + "/css_variables.source.less", + string: result.cssVariablesSource + }); + const libCssVars = new Resource({ + path: themeDir + "/css_variables.css", + string: result.cssVariables + }); + const libCssSkel = new Resource({ + path: themeDir + "/library_skeleton.css", + string: result.cssSkeleton + }); + const libCssSkelRtl = new Resource({ + path: themeDir + "/library_skeleton-RTL.css", + string: result.cssSkeletonRtl + }); + + files.push(libCssVarsSource, libCssVars, libCssSkel, libCssSkelRtl); + } + }, (err) => { + log.error(`Error while compiling ${resource.getPath()}: ${err.message}`); + throw err; + }); + }; + + return Promise.all(resources.map(compile)).then(() => { + return files; + }); + } + + /** + * Clears all cached build results. + * + * Use this method to prevent high memory consumption when building many themes within the same process. + * + * @public + */ + clearCache() { + this.builder.clearCache(); + } +} + +/** + * + * @public + * @typedef {object} ThemeBuilderOptions + * @property {boolean} [compress=false] Compress build output (CSS / JSON) + * @property {boolean} [cssVariables=false] Generates the CSS variables + * (css-variables.css, css-variables.source.less) and the skeleton for a theme + * (library-skeleton.css, [library-skeleton-RTL.css]) + */ + +/** + * Builds a library theme. + * + * @public + * @function default + * @static + * + * @alias @ui5/builder/processors/themeBuilder + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of library.source.less + * resources to be processed + * @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom + * [fs interface]{@link module:@ui5/fs/fsInterface} + * @param {module:@ui5/builder/processors/ThemeBuilder~ThemeBuilderOptions} [parameters.options] Options + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with theme resources + */ +export default async function({ + resources, + fs, + options = {} +}) { + const {compress, cssVariables} = + /** @type {module:@ui5/builder/processors/ThemeBuilder~ThemeBuilderOptions} */ (options); + const themeBuilder = new ThemeBuilder({fs}); + return themeBuilder.build(resources, { + compress, + cssVariables + }).then((files) => { + themeBuilder.clearCache(); + return files; + }); +} diff --git a/packages/builder/lib/processors/themeBuilderWorker.js b/packages/builder/lib/processors/themeBuilderWorker.js new file mode 100644 index 00000000000..91956e566c4 --- /dev/null +++ b/packages/builder/lib/processors/themeBuilderWorker.js @@ -0,0 +1,278 @@ +import workerpool from "workerpool"; +import themeBuilder from "./themeBuilder.js"; +import {createResource} from "@ui5/fs/resourceFactory"; +import {Buffer} from "node:buffer"; + +/** + * Task to build library themes. + * + * @private + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {MessagePort} parameters.fsInterfacePort + * @param {object[]} parameters.themeResources Input array of Uint8Array transferable objects + * that are the less sources to build upon. By nature those are @ui5/fs/Resource. + * @param {object} parameters.options Less compiler options + * @returns {Promise} Resulting array of Uint8Array transferable objects + */ +export default async function execThemeBuild({ + fsInterfacePort, + themeResources = [], + options = {} +}) { + const fsThemeResources = deserializeResources(themeResources); + const fsReader = new FsWorkerThreadInterface(fsInterfacePort); + + const result = await themeBuilder({ + resources: fsThemeResources, + fs: fsReader, + options + }); + + return serializeResources(result); +} + +/** + * Casts @ui5/fs/Resource-s into an Uint8Array transferable object + * + * @param {@ui5/fs/Resource[]} resourceCollection + * @returns {Promise} + */ +export async function serializeResources(resourceCollection) { + return Promise.all( + resourceCollection.map(async (res) => ({ + buffer: await res.getBuffer(), + path: res.getPath() + })) + ); +} + +/** + * Casts Uint8Array into @ui5/fs/Resource-s transferable object + * + * @param {Promise} resources + * @returns {@ui5/fs/Resource[]} + */ +export function deserializeResources(resources) { + return resources.map((res) => { + // res.buffer is an Uint8Array object and needs to be cast + // to a Buffer in order to be read correctly. + return createResource({path: res.path, buffer: Buffer.from(res.buffer)}); + }); +} + +/** + * "@ui5/fs/fsInterface" like class that uses internally + * "@ui5/fs/fsInterface", implements its methods, and + * sends the results to a MessagePort. + * + * Used in the main thread in a combination with FsWorkerThreadInterface. + */ +export class FsMainThreadInterface { + #comPorts = new Set(); + #fsInterfaceReader = null; + #cache = Object.create(null); + + /** + * Constructor + * + * @param {@ui5/fs/fsInterface} fsInterfaceReader Reader for the Resources + */ + constructor(fsInterfaceReader) { + if (!fsInterfaceReader) { + throw new Error("fsInterfaceReader is mandatory argument"); + } + + this.#fsInterfaceReader = fsInterfaceReader; + } + + /** + * Adds MessagePort and starts listening for requests on it. + * + * @param {MessagePort} comPort port1 from a {code}MessageChannel{/code} + */ + startCommunication(comPort) { + if (!comPort) { + throw new Error("Communication channel is mandatory argument"); + } + + this.#comPorts.add(comPort); + comPort.on("message", (e) => this.#onMessage(e, comPort)); + comPort.on("close", () => comPort.close()); + } + + /** + * Ends MessagePort communication. + * + * @param {MessagePort} comPort port1 to remove from handling. + */ + endCommunication(comPort) { + comPort.close(); + this.#comPorts.delete(comPort); + } + + /** + * Destroys the FsMainThreadInterface + */ + cleanup() { + this.#comPorts.forEach((comPort) => comPort.close()); + this.#cache = null; + this.#fsInterfaceReader = null; + } + + /** + * Handles messages from the MessagePort + * + * @param {object} e data to construct the request + * @param {string} e.action Action to perform. Corresponds to the names of + * the public methods of "@ui5/fs/fsInterface" + * @param {string} e.fsPath Path of the Resource + * @param {object} e.options Options for "readFile" action + * @param {MessagePort} comPort The communication channel + */ + #onMessage(e, comPort) { + switch (e.action) { + case "readFile": + this.#doRequest(comPort, {action: "readFile", fsPath: e.fsPath, options: e.options}); + break; + case "stat": + this.#doRequest(comPort, {action: "stat", fsPath: e.fsPath}); + break; + } + } + + /** + * Requests a Resource from the "@ui5/fs/fsInterface" and sends it to the worker threads + * via postMessage. + * + * @param {MessagePort} comPort The communication channel + * @param {object} parameters + * @param {string} parameters.action Action to perform. Corresponds to the names of + * the public methods of "@ui5/fs/fsInterface" and triggers this method of the + * "@ui5/fs/fsInterface" instance. + * @param {string} parameters.fsPath Path of the Resource + * @param {object} parameters.options Options for "readFile" action + */ + async #doRequest(comPort, {action, fsPath, options}) { + const cacheKey = `${fsPath}-${action}`; + if (!this.#cache[cacheKey]) { + this.#cache[cacheKey] = new Promise((res) => { + if (action === "readFile") { + this.#fsInterfaceReader.readFile(fsPath, options, (error, result) => res({error, result})); + } else if (action === "stat") { + this.#fsInterfaceReader.stat(fsPath, (error, result) => + // The Stat object has some special methods that sometimes cannot be serialized + // properly in the postMessage. In this scenario, we do not need those methods, + // but just to check whether stats has resolved to something. + res(JSON.parse(JSON.stringify({error, result}))) + ); + } else { + res({error: new Error(`Action "${action}" is not available.`), result: null}); + } + }); + } + + const fromCache = await this.#cache[cacheKey]; + comPort.postMessage({action, fsPath, ...fromCache}); + } +} + +/** + * "@ui5/fs/fsInterface" like class that uses internally + * "@ui5/fs/fsInterface", implements its methods, and + * requests resources via MessagePort. + * + * Used in the main thread in a combination with FsMainThreadInterface. + */ +export class FsWorkerThreadInterface { + #comPort = null; + #callbacks = []; + #cache = Object.create(null); + + /** + * Constructor + * + * @param {MessagePort} comPort Communication port + */ + constructor(comPort) { + if (!comPort) { + throw new Error("Communication port is mandatory argument"); + } + + this.#comPort = comPort; + comPort.on("message", this.#onMessage.bind(this)); + comPort.on("close", this.#onClose.bind(this)); + } + + /** + * Handles messages from MessagePort + * + * @param {object} e + * @param {string} e.action Action to perform. Corresponds to the names of + * the public methods of "@ui5/fs/fsInterface" + * @param {string} e.fsPath Path of the Resource + * @param {*} e.result Response from the "action". + * @param {object} e.error Error from the "action". + */ + #onMessage(e) { + const cbObject = this.#callbacks.find((cb) => cb.action === e.action && cb.fsPath === e.fsPath); + + if (cbObject) { + this.#cache[`${e.fsPath}-${e.action}`] = {error: e.error, result: e.result}; + this.#callbacks.splice(this.#callbacks.indexOf(cbObject), 1); + cbObject.callback(e.error, e.result); + } else { + throw new Error("No callback found for this message! Possible hang for the thread!", e); + } + } + + /** + * End communication + */ + #onClose() { + this.#comPort.close(); + this.#cache = null; + } + + /** + * Makes a request via the MessagePort + * + * @param {object} parameters + * @param {string} parameters.action Action to perform. Corresponds to the names of + * the public methods. + * @param {string} parameters.fsPath Path of the Resource + * @param {object} parameters.options Options for "readFile" action + * @param {Function} callback Callback to call when the "action" is executed and ready. + */ + #doRequest({action, fsPath, options}, callback) { + const cacheKey = `${fsPath}-${action}`; + + if (this.#cache[cacheKey]) { + const {result, error} = this.#cache[cacheKey]; + callback(error, result); + } else { + this.#callbacks.push({action, fsPath, callback}); + this.#comPort.postMessage({action, fsPath, options}); + } + } + + readFile(fsPath, options, callback) { + this.#doRequest({action: "readFile", fsPath, options}, callback); + } + + stat(fsPath, callback) { + this.#doRequest({action: "stat", fsPath}, callback); + } +} + +// Test execution via ava is never done on the main thread +/* istanbul ignore else */ +if (!workerpool.isMainThread) { + // Script got loaded through workerpool + // => Create a worker and register public functions + workerpool.worker({ + execThemeBuild + }); +} diff --git a/packages/builder/lib/processors/versionInfoGenerator.js b/packages/builder/lib/processors/versionInfoGenerator.js new file mode 100644 index 00000000000..a846177cf61 --- /dev/null +++ b/packages/builder/lib/processors/versionInfoGenerator.js @@ -0,0 +1,469 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:processors:versionInfoGenerator"); +import {createResource} from "@ui5/fs/resourceFactory"; +import posixPath from "node:path/posix"; + +/** + * @public + * @module @ui5/builder/processors/versionInfoGenerator + */ + +function pad(v) { + return String(v).padStart(2, "0"); +} +function getTimestamp() { + const date = new Date(); + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + // yyyyMMddHHmm + return year + month + day + hours + minutes; +} + +/** + * Manifest libraries as defined in the manifest.json file + * + * @typedef {Object} ManifestLibraries + * + * sample: + *
    + * {
    + * 	"sap.chart": {
    + * 		"lazy": true
    + * 	},
    + * 	"sap.f": { }
    + * }
    + * 
    + */ + +/** + * Extracted information from a manifest's sap.app and sap.ui5 sections. + * + * @typedef {object} ManifestInfo + * + * @property {string} id The library name, e.g. "lib.x" + * @property {string} embeddedBy the library this component is embedded in, e.g. "lib.x" + * @property {string[]} embeds the embedded component names, e.g. ["lib.x.sub"] + * @property {module:@ui5/builder/processors/versionInfoGenerator~ManifestLibraries} libs the dependencies, e.g. + * {"sap.chart":{"lazy": true}, "sap.f":{}} + */ + + +/** + * Processes manifest resource and extracts information. + * + * @param {@ui5/fs/Resource} manifestResource + * @returns {Promise} + */ +const processManifest = async (manifestResource) => { + const manifestContent = await manifestResource.getString(); + const manifestObject = JSON.parse(manifestContent); + const manifestInfo = Object.create(null); + + // sap.ui5/dependencies is used for the "manifestHints/libs" + if (manifestObject["sap.ui5"]) { + const manifestDependencies = manifestObject["sap.ui5"]["dependencies"]; + if (manifestDependencies && manifestDependencies.libs) { + const libs = Object.create(null); + for (const [libKey, libValue] of Object.entries(manifestDependencies.libs)) { + libs[libKey] = Object.create(null); + if (libValue.lazy) { + libs[libKey].lazy = true; + } + } + manifestInfo.libs = libs; + } + } + + // sap.app/embeds, sap.app/embeddedBy and sap.app/id is used for "components" + if (manifestObject["sap.app"]) { + const manifestEmbeds = manifestObject["sap.app"]["embeds"]; + manifestInfo.embeds = manifestEmbeds; + + const manifestEmbeddedBy = manifestObject["sap.app"]["embeddedBy"]; + manifestInfo.embeddedBy = manifestEmbeddedBy; + + const id = manifestObject["sap.app"]["id"]; + manifestInfo.id = id; + } + return manifestInfo; +}; + +/** + * Checks if a component (componentPath) is bundled with the library (embeddedBy) + * + * @param {string} embeddedBy e.g. "../" + * @param {string} componentPath e.g. "lib/x/sub" + * @param {string} libraryPathPrefix e.g. "lib/x" + * @returns {boolean} whether or not this component is bundled with the library + */ +const isBundledWithLibrary = (embeddedBy, componentPath, libraryPathPrefix) => { + if (typeof embeddedBy === "undefined") { + log.verbose(" Component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); + return false; + } + if (typeof embeddedBy !== "string") { + log.error( + ` Component '${componentPath}': property 'sap.app/embeddedBy' is of type '${typeof embeddedBy}' ` + + `(expected 'string'), it won't be listed as 'embedded'`); + return false; + } + if ( !embeddedBy.length ) { + log.error( + ` Component '${componentPath}': property 'sap.app/embeddedBy' has an empty string value ` + + `(which is invalid), it won't be listed as 'embedded'` + ); + return false; + } + let resolvedEmbeddedBy = posixPath.resolve(componentPath, embeddedBy); + if ( resolvedEmbeddedBy && !resolvedEmbeddedBy.endsWith("/") ) { + resolvedEmbeddedBy = resolvedEmbeddedBy + "/"; + } + if ( libraryPathPrefix === resolvedEmbeddedBy ) { + log.verbose(" Component's 'sap.app/embeddedBy' property points to library, list it as 'embedded'"); + return true; + } else { + log.verbose( + ` Component's 'sap.app/embeddedBy' points to '${resolvedEmbeddedBy}', don't list it as 'embedded'`); + return false; + } +}; + +/** + * Retrieves the manifest path of a subcomponent + * + * @param {string} filePath path to the manifest, e.g. "lib/x/manifest.json" + * @param {string} subPath relative sub path, e.g. "sub" + * @returns {string} manifest path, e.g. "lib/x/sub/manifest.json" + */ +const getManifestPath = (filePath, subPath) => { + return posixPath.resolve(posixPath.dirname(filePath), subPath, "manifest.json"); +}; + +/** + * Represents dependency information for a library. + * Dependencies can be retrieved using #getResolvedLibraries + * and with that are resolved recursively + */ +class DependencyInfo { + /** + * + * @param {module:@ui5/builder/processors/versionInfoGenerator~ManifestLibraries} libs + * @param {string} name library name, e.g. "lib.x" + */ + constructor(libs, name) { + this.libs = libs; + this.name = name; + } + + /** + * Add library to libsResolved and if already present + * merge lazy property + * + * @param {string} libName library name, e.g. "lib.x" + * @param {boolean} lazy + * @returns {{lazy: boolean}} the added library + */ + addResolvedLibDependency(libName, lazy) { + let alreadyResolved = this._libsResolved[libName]; + if (!alreadyResolved) { + alreadyResolved = Object.create(null); + if (lazy) { + alreadyResolved.lazy = true; + } + this._libsResolved[libName] = alreadyResolved; + } else { + // siblings if sibling is eager only if one other sibling eager + alreadyResolved.lazy = alreadyResolved.lazy && lazy; + } + return alreadyResolved; + } + + /** + * Resolves dependencies recursively and retrieves them with + * - resolved siblings a lazy and a eager dependency becomes eager + * - resolved children become lazy if their parent is lazy + * + * @param {Map} dependencyInfoMap + * @returns {module:@ui5/builder/processors/versionInfoGenerator~ManifestLibraries} resolved libraries + */ + getResolvedLibraries(dependencyInfoMap) { + if (!this._libsResolved) { + // early set if there is a potential cycle + this._libsResolved = Object.create(null); + if (!this.libs) { + return this._libsResolved; + } + for (const [libName, libValue] of Object.entries(this.libs)) { + const lazy = libValue.lazy; + const dependencyInfoObjectAdded = this.addResolvedLibDependency(libName, lazy); + const dependencyInfo = dependencyInfoMap.get(libName); + if (dependencyInfo) { + const childLibsResolved = dependencyInfo.getResolvedLibraries(dependencyInfoMap); + + // children if parent is lazy children become lazy + for (const [resolvedLibName, resolvedLib] of Object.entries(childLibsResolved)) { + this.addResolvedLibDependency(resolvedLibName, + resolvedLib.lazy || dependencyInfoObjectAdded.lazy); + } + } else { + log.info(`Cannot find dependency '${libName}' `+ + `defined in the manifest.json or .library file of project '${this.name}'. ` + + "This might prevent some UI5 runtime performance optimizations from taking effect. " + + "Please double check your project's dependency configuration."); + } + } + } + return this._libsResolved; + } +} + + +/** + * Sorts the keys of a given object + * + * @param {object} obj the object + * @returns {object} the object with sorted keys + */ +const sortObjectKeys = (obj) => { + const sortedObject = Object.create(null); + const keys = Object.keys(obj); + keys.sort(); + keys.forEach((key) => { + sortedObject[key] = obj[key]; + }); + return sortedObject; +}; + +/** + * Builds the manifestHints object from the dependencyInfo + * + * @param {DependencyInfo} dependencyInfo + * @param {Map} dependencyInfoMap + * @returns {{dependencies: {libs: ManifestLibraries}}} manifestHints + */ +const getManifestHints = (dependencyInfo, dependencyInfoMap) => { + if (dependencyInfo) { + const libsResolved = dependencyInfo.getResolvedLibraries(dependencyInfoMap); + if (libsResolved && Object.keys(libsResolved).length) { + return { + dependencies: { + libs: sortObjectKeys(libsResolved) + } + }; + } + } +}; + +/** + * Common type for Library and Component + * embeds and bundled components make only sense for library + * + * @typedef {object} ArtifactInfo + * @property {string} componentName The library name, e.g. "lib.x" + * @property {Set} bundledComponents The embedded components which have an embeddedBy reference to the library + * @property {DependencyInfo} dependencyInfo The dependency info object + * @property {module:@ui5/builder/processors/versionInfoGenerator~ArtifactInfo[]} embeds The embedded artifact infos + */ + + +/** + * Processes the manifest and creates a ManifestInfo and an ArtifactInfo. + * + * @param {@ui5/fs/Resource} libraryManifest + * @param {string} [name] library name, if not provided using the ManifestInfo's id + * @returns {Promise<{manifestInfo: ManifestInfo, libraryArtifactInfo: ArtifactInfo}>} + */ +async function processManifestAndGetArtifactInfo(libraryManifest, name) { + const manifestInfo = await processManifest(libraryManifest); + name = name || manifestInfo.id; + const libraryArtifactInfo = Object.create(null); + libraryArtifactInfo.componentName = name; + libraryArtifactInfo.dependencyInfo = new DependencyInfo(manifestInfo.libs, name); + return {manifestInfo, libraryArtifactInfo}; +} + +/** + * Processes the library info and fills the maps dependencyInfoMap and embeddedInfoMap. + * + * @param {module:@ui5/builder/processors/versionInfoGenerator~LibraryInfo} libraryInfo + * @returns {Promise} + */ +const processLibraryInfo = async (libraryInfo) => { + if (!libraryInfo.libraryManifest) { + log.verbose( + `Cannot add meta information for library '${libraryInfo.name}'. The manifest.json file cannot be found`); + return; + } + + const {manifestInfo, libraryArtifactInfo} = + await processManifestAndGetArtifactInfo(libraryInfo.libraryManifest, libraryInfo.name); + + const bundledComponents = new Set(); + libraryArtifactInfo.bundledComponents = bundledComponents; + + const embeds = manifestInfo.embeds||[]; // e.g. ["sub"] + // filter only embedded manifests + const embeddedPaths = embeds.map((embed) => { + return getManifestPath(libraryInfo.libraryManifest.getPath(), embed); + }); + // e.g. manifest resource with lib/x/sub/manifest.json + let embeddedManifests = libraryInfo.embeddedManifests || []; + embeddedManifests = embeddedManifests.filter((manifestResource) => { + return embeddedPaths.includes(manifestResource.getPath()); + }); + + // get all embedded manifests + const embeddedManifestPromises = embeddedManifests.map(async (embeddedManifest) => { + const {manifestInfo: embeddedManifestInfo, libraryArtifactInfo: embeddedArtifactInfo} = + await processManifestAndGetArtifactInfo(embeddedManifest); + + const componentName = embeddedManifestInfo.id; + + const embeddedManifestDirName = posixPath.dirname(embeddedManifest.getPath()); + const libraryManifestDirName = posixPath.dirname(libraryInfo.libraryManifest.getPath()); + + if (isBundledWithLibrary(embeddedManifestInfo.embeddedBy, embeddedManifestDirName, + libraryManifestDirName + "/")) { + bundledComponents.add(componentName); + } + return embeddedArtifactInfo; + }); + + const embeddedArtifactInfos = await Promise.all(embeddedManifestPromises); + libraryArtifactInfo.embeds = embeddedArtifactInfos; + + return libraryArtifactInfo; +}; + +/** + * Library Info + * + * contains information about the name and the version of the library and its manifest, as well as the nested manifests. + * + * @public + * @typedef {object} LibraryInfo + * @property {string} name The library name, e.g. "lib.x" + * @property {string} version The library version, e.g. "1.0.0" + * @property {@ui5/fs/Resource} libraryManifest library manifest resource, + * e.g. resource with path "lib/x/manifest.json" + * @property {@ui5/fs/Resource[]} embeddedManifests list of embedded manifest resources, + * e.g. resource with path "lib/x/sub/manifest.json" + */ + +/** + * Creates sap-ui-version.json. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {object} parameters.options Options + * @param {string} parameters.options.rootProjectName Name of the root project + * @param {string} parameters.options.rootProjectVersion Version of the root project + * @param {module:@ui5/builder/processors/versionInfoGenerator~LibraryInfo[]} parameters.options.libraryInfos Array of + * objects representing libraries, + * e.g.
    + *   {
    + *      name: "lib.x",
    + *      version: "1.0.0",
    + *      libraryManifest: @ui5/fs/Resource,
    + *      embeddedManifests: @ui5/fs/Resource[]
    + *   }
    + * 
    + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with an array containing the versionInfo resource + */ + +export default async function({options}) { + if (!options.rootProjectName || options.rootProjectVersion === undefined || options.libraryInfos === undefined) { + throw new Error("[versionInfoGenerator]: Missing options parameters"); + } + + const buildTimestamp = getTimestamp(); + + /** + * componentName to dependency info + * + * @type {Map} + */ + const dependencyInfoMap = new Map(); + + + // process library infos + const libraryInfosProcessPromises = options.libraryInfos.map((libraryInfo) => { + return processLibraryInfo(libraryInfo); + }); + + let artifactInfos = await Promise.all(libraryInfosProcessPromises); + artifactInfos = artifactInfos.filter(Boolean); + + // fill dependencyInfoMap + artifactInfos.forEach((artifactInfo) => { + dependencyInfoMap.set(artifactInfo.componentName, artifactInfo.dependencyInfo); + }); + + + const libraries = options.libraryInfos.map((libraryInfo) => { + const library = { + name: libraryInfo.name, + version: libraryInfo.version, + buildTimestamp: buildTimestamp, + scmRevision: ""// TODO: insert current library scm revision here + }; + + const dependencyInfo = dependencyInfoMap.get(libraryInfo.name); + const manifestHints = getManifestHints(dependencyInfo, dependencyInfoMap); + if (manifestHints) { + library.manifestHints = manifestHints; + } + return library; + }); + + // sort libraries alphabetically + libraries.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + + // components + let components; + artifactInfos.forEach((artifactInfo) => { + artifactInfo.embeds.forEach((embeddedArtifactInfo) => { + const componentObject = Object.create(null); + const bundledComponents = artifactInfo.bundledComponents; + const componentName = embeddedArtifactInfo.componentName; + if (!bundledComponents.has(componentName)) { + componentObject.hasOwnPreload = true; + } + componentObject.library = artifactInfo.componentName; + + const manifestHints = getManifestHints(embeddedArtifactInfo.dependencyInfo, dependencyInfoMap); + if (manifestHints) { + componentObject.manifestHints = manifestHints; + } + + components = components || Object.create(null); + components[componentName] = componentObject; + }); + }); + + // sort components alphabetically + components = components && sortObjectKeys(components); + + const versionJson = { + name: options.rootProjectName, + version: options.rootProjectVersion, // TODO: insert current application version here + buildTimestamp: buildTimestamp, + scmRevision: "", // TODO: insert current application scm revision here + // gav: "", // TODO: insert current application id + version here + libraries, + components + }; + + return [createResource({ + path: "/resources/sap-ui-version.json", + string: JSON.stringify(versionJson, null, "\t") + })]; +} diff --git a/packages/builder/lib/tasks/buildThemes.js b/packages/builder/lib/tasks/buildThemes.js new file mode 100644 index 00000000000..d407bb97ff7 --- /dev/null +++ b/packages/builder/lib/tasks/buildThemes.js @@ -0,0 +1,237 @@ +import path from "node:path"; +import fsInterface from "@ui5/fs/fsInterface"; +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:buildThemes"); +import {fileURLToPath} from "node:url"; +import os from "node:os"; +import workerpool from "workerpool"; +import {deserializeResources, serializeResources, FsMainThreadInterface} from "../processors/themeBuilderWorker.js"; +import {setTimeout as setTimeoutPromise} from "node:timers/promises"; + +let pool; + +function getPool(taskUtil) { + if (!pool) { + const MIN_WORKERS = 2; + const MAX_WORKERS = 4; + const osCpus = os.cpus().length || 1; + const maxWorkers = Math.max(Math.min(osCpus - 1, MAX_WORKERS), MIN_WORKERS); + + log.verbose(`Creating workerpool with up to ${maxWorkers} workers (available CPU cores: ${osCpus})`); + const workerPath = fileURLToPath(new URL("../processors/themeBuilderWorker.js", import.meta.url)); + pool = workerpool.pool(workerPath, { + workerType: "thread", + maxWorkers + }); + taskUtil.registerCleanupTask((force) => { + const attemptPoolTermination = async () => { + log.verbose(`Attempt to terminate the workerpool...`); + + if (!pool) { + return; + } + + // There are many stats that could be used, but these ones seem the most + // convenient. When all the (available) workers are idle, then it's safe to terminate. + let {idleWorkers, totalWorkers} = pool.stats(); + while (idleWorkers !== totalWorkers && !force) { + await setTimeoutPromise(100); // Wait a bit workers to finish and try again + + if (!pool) { // pool might have been terminated in the meantime + return; + } + ({idleWorkers, totalWorkers} = pool.stats()); + } + + const poolToBeTerminated = pool; + pool = null; + return poolToBeTerminated.terminate(force); + }; + + return attemptPoolTermination(); + }); + } + return pool; +} + +async function buildThemeInWorker(taskUtil, options, transferList) { + const toTransfer = transferList ? {transfer: transferList} : undefined; + + return getPool(taskUtil).exec("execThemeBuild", [options], toTransfer); +} + + +/** + * @public + * @module @ui5/builder/tasks/buildThemes + */ +/** + * Task to build a library theme. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {@ui5/builder/tasks/TaskUtil|object} [parameters.taskUtil] TaskUtil instance. + * Required to run buildThemes in parallel execution mode. + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.inputPattern Search pattern for *.less files to be built + * @param {string} [parameters.options.librariesPattern] Search pattern for .library files + * @param {string} [parameters.options.themesPattern] Search pattern for sap.ui.core theme folders + * @param {boolean} [parameters.options.compress=true] + * @param {boolean} [parameters.options.cssVariables=false] + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({ + workspace, dependencies, taskUtil, + options: { + projectName, inputPattern, librariesPattern, themesPattern, compress, + cssVariables + } +}) { + const combo = new ReaderCollectionPrioritized({ + name: `theme - prioritize workspace over dependencies: ${projectName}`, + readers: [workspace, dependencies] + }); + + compress = compress === undefined ? true : compress; + + const pThemeResources = workspace.byGlob(inputPattern); + let pAvailableLibraries; + let pAvailableThemes; + if (librariesPattern) { + // If a librariesPattern is given + // we will use it to reduce the set of libraries a theme will be built for + pAvailableLibraries = combo.byGlob(librariesPattern); + } + if (themesPattern) { + // If a themesPattern is given + // we will use it to reduce the set of themes that will be built + pAvailableThemes = combo.byGlob(themesPattern, {nodir: false}); + } + + /* Don't try to build themes for libraries that are not available + (maybe replace this with something more aware of which dependencies are optional and therefore + legitimately missing and which not (fault case)) + */ + let availableLibraries; + if (pAvailableLibraries) { + availableLibraries = []; + (await pAvailableLibraries).forEach((resource) => { + const library = path.dirname(resource.getPath()); + if (!availableLibraries.includes(library)) { + availableLibraries.push(library); + } + }); + } + let availableThemes; + if (pAvailableThemes) { + availableThemes = (await pAvailableThemes) + .filter((resource) => resource.getStatInfo().isDirectory()) + .map((resource) => { + return path.basename(resource.getPath()); + }); + } + + let themeResources = await pThemeResources; + + const isAvailable = function(resource) { + let libraryAvailable = false; + let themeAvailable = false; + const resourcePath = resource.getPath(); + const themeName = path.basename(path.dirname(resourcePath)); + + if (!availableLibraries || availableLibraries.length === 0) { + libraryAvailable = true; // If no libraries are found, build themes for all libraries + } else { + for (let i = availableLibraries.length - 1; i >= 0; i--) { + if (resourcePath.startsWith(availableLibraries[i])) { + libraryAvailable = true; + } + } + } + + if (!availableThemes || availableThemes.length === 0) { + themeAvailable = true; // If no themes are found, build all themes + } else { + themeAvailable = availableThemes.includes(themeName); + } + + if (log.isLevelEnabled("verbose")) { + if (!libraryAvailable) { + log.silly(`Skipping ${resourcePath}: Library is not available`); + } + if (!themeAvailable) { + log.verbose(`Skipping ${resourcePath}: sap.ui.core theme '${themeName}' is not available. ` + + "If you experience missing themes, check whether you have added the corresponding theme " + + "library to your projects dependencies and make sure that your custom themes contain " + + "resources for the sap.ui.core namespace."); + } + } + + // Only build if library and theme are available + return libraryAvailable && themeAvailable; + }; + + if (availableLibraries || availableThemes) { + if (log.isLevelEnabled("verbose")) { + log.verbose("Filtering themes to be built:"); + if (availableLibraries) { + log.verbose(`Available libraries: ${availableLibraries.join(", ")}`); + } + if (availableThemes) { + log.verbose(`Available sap.ui.core themes: ${availableThemes.join(", ")}`); + } + } + themeResources = themeResources.filter(isAvailable); + } + + let processedResources; + const useWorkers = !!taskUtil; + if (useWorkers) { + const threadMessageHandler = new FsMainThreadInterface(fsInterface(combo)); + + processedResources = await Promise.all(themeResources.map(async (themeRes) => { + const {port1, port2} = new MessageChannel(); + threadMessageHandler.startCommunication(port1); + + const result = await buildThemeInWorker(taskUtil, { + fsInterfacePort: port2, + themeResources: await serializeResources([themeRes]), + options: { + compress, + cssVariables: !!cssVariables, + }, + }, [port2]); + + threadMessageHandler.endCommunication(port1); + + return result; + })) + .then((resources) => Array.prototype.concat.apply([], resources)) + .then(deserializeResources); + + threadMessageHandler.cleanup(); + } else { + // Do not use workerpool + const themeBuilder = (await import("../processors/themeBuilder.js")).default; + + processedResources = await themeBuilder({ + resources: themeResources, + fs: fsInterface(combo), + options: { + compress, + cssVariables: !!cssVariables, + } + }); + } + + await Promise.all(processedResources.map((resource) => { + return workspace.write(resource); + })); +} diff --git a/packages/builder/lib/tasks/bundlers/generateBundle.js b/packages/builder/lib/tasks/bundlers/generateBundle.js new file mode 100644 index 00000000000..12c2841c5fb --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/generateBundle.js @@ -0,0 +1,134 @@ +import moduleBundler from "../../processors/bundlers/moduleBundler.js"; +import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js"; +import createModuleNameMapping from "./utils/createModuleNameMapping.js"; +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; + +/** + * @public + * @module @ui5/builder/tasks/bundlers/generateBundle + */ + +/* eslint-disable max-len */ +/** + * Generates a bundle based on the given bundle definition + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/ReaderCollection} parameters.dependencies Collection to read dependency files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinition} parameters.options.bundleDefinition Module + * bundle definition + * @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleOptions} [parameters.options.bundleOptions] Module + * bundle options + * @returns {Promise} Promise resolving with undefined once data has been written + */ +/* eslint-enable max-len */ +export default async function({ + workspace, dependencies, taskUtil, options: {projectName, bundleDefinition, bundleOptions} +}) { + let combo = new ReaderCollectionPrioritized({ + name: `generateBundle - prioritize workspace over dependencies: ${projectName}`, + readers: [workspace, dependencies] + }); + + const optimize = !bundleOptions || bundleOptions.optimize !== false; + if (taskUtil) { + /* Scenarios + 1. Optimize bundle with minification already done + Workspace: + * /resources/my/lib/Control.js [ui5:HasDebugVariant] + * /resources/my/lib/Control.js.map [ui5:HasDebugVariant] + * /resources/my/lib/Control-dbg.js [ui5:IsDebugVariant] + + Bundler input: + * /resources/my/lib/Control.js + * /resources/my/lib/Control.js.map + + => Filter out debug resources + + 2. Optimize bundle with no minification + * /resources/my/lib/Control.js + + => No action necessary + + 3. Debug-bundle with minification already done + Workspace: + * /resources/my/lib/Control.js [ui5:HasDebugVariant] + * /resources/my/lib/Control.js.map [ui5:HasDebugVariant] + * /resources/my/lib/Control-dbg.js [ui5:IsDebugVariant] + + Bundler input: + * /resources/my/lib/Control-dbg.js + * moduleNameMapping: [{"/resources/my/lib/Control-dbg.js": "my/lib/Control.js"}] + + => Filter out minified-resources (tagged as "HasDebugVariant", incl. source maps) and rename debug-files + + 4. Debug-bundle with no minification + * /resources/my/lib/Control.js + + => No action necessary + + 5. Bundle with external input (optimize or not), e.g. TS-project + Workspace: + * /resources/my/lib/Control.ts + * /resources/my/lib/Control.js + * /resources/my/lib/Control.js.map + + Bundler input: + * /resources/my/lib/Control.js + * /resources/my/lib/Control.js.map + */ + + // Omit -dbg files for optimize bundles and vice versa + const filterTag = optimize ? + taskUtil.STANDARD_TAGS.IsDebugVariant : taskUtil.STANDARD_TAGS.HasDebugVariant; + combo = taskUtil.resourceFactory.createFilterReader({ + reader: combo, + callback: function(resource) { + return !taskUtil.getTag(resource, filterTag); + } + }); + } + const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion(); + const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0"); + return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}").then((resources) => { + const options = { + bundleDefinition: applyDefaultsToBundleDefinition(bundleDefinition, taskUtil), + bundleOptions, + allowStringBundling + }; + if (!optimize && taskUtil) { + options.moduleNameMapping = createModuleNameMapping({resources, taskUtil}); + } + if (coreVersion) { + options.targetUi5CoreVersion = coreVersion; + } + return moduleBundler({options, resources}).then((bundles) => { + return Promise.all(bundles.map(({bundle, sourceMap} = {}) => { + if (!bundle) { + // Skip empty bundles + return; + } + if (taskUtil) { + taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle); + if (sourceMap) { + // Clear tag that might have been set by the minify task, in cases where + // the bundle name is identical to a source file + taskUtil.clearTag(sourceMap, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + } + const writes = [workspace.write(bundle)]; + if (sourceMap) { + writes.push(workspace.write(sourceMap)); + } + return Promise.all(writes); + })); + }); + }); +} diff --git a/packages/builder/lib/tasks/bundlers/generateComponentPreload.js b/packages/builder/lib/tasks/bundlers/generateComponentPreload.js new file mode 100644 index 00000000000..e5eff43a35e --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/generateComponentPreload.js @@ -0,0 +1,184 @@ +import path from "node:path"; +import moduleBundler from "../../processors/bundlers/moduleBundler.js"; +import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:bundlers:generateComponentPreload"); +import {negateFilters} from "../../lbt/resources/ResourceFilterList.js"; + +/** + * @public + * @module @ui5/builder/tasks/bundlers/generateComponentPreload + */ + +/** + * Task to for application bundling. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns) + * that should be excluded. + * A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk, + * denote an arbitrary number of characters or folder names. + * Re-includes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later + * inclusion overrides an earlier exclusion, and vice versa. + * @param {string[]} [parameters.options.paths] Array of paths (or glob patterns) for component files + * @param {string[]} [parameters.options.namespaces] Array of component namespaces + * @param {string[]} [parameters.options.skipBundles] Names of bundles that should not be created + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({ + workspace, taskUtil, options: {projectName, paths, namespaces, skipBundles = [], excludes = []} +}) { + let nonDbgWorkspace = workspace; + if (taskUtil) { + nonDbgWorkspace = taskUtil.resourceFactory.createFilterReader({ + reader: workspace, + callback: function(resource) { + // Remove any debug variants + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.IsDebugVariant); + } + }); + } + + return nonDbgWorkspace.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}") + .then(async (resources) => { + let allNamespaces = []; + if (paths) { + allNamespaces = await Promise.all(paths.map(async (componentPath) => { + const globPath = "/resources/" + componentPath; + log.verbose(`Globbing for Components directories with configured path ${globPath}...`); + const components = await nonDbgWorkspace.byGlob(globPath); + return components.map((component) => { + const compDir = path.dirname(component.getPath()).replace(/^\/resources\//i, ""); + log.verbose(`Found component namespace ${compDir}`); + return compDir; + }); + })); + } + if (namespaces) { + allNamespaces.push(...namespaces); + } + + allNamespaces = Array.prototype.concat.apply([], allNamespaces); + // As this task is often called with a single namespace, also check + // for bad calls like "namespaces: [undefined]" + if (!allNamespaces || !allNamespaces.length || !allNamespaces[0]) { + throw new Error("generateComponentPreload: No component namespace(s) " + + `found for project: ${projectName}`); + } + + const allFilterExcludes = negateFilters(excludes); + const unusedFilterExcludes = new Set(allFilterExcludes); + + const bundleDefinitions = allNamespaces.map((namespace) => { + const bundleName = `${namespace}/Component-preload.js`; + if (skipBundles.includes(bundleName)) { + log.verbose(`Skipping generation of bundle ${bundleName}`); + return null; + } + + const filters = [ + `${namespace}/`, + `${namespace}/**/manifest.json`, + `${namespace}/changes/changes-bundle.json`, + `${namespace}/changes/flexibility-bundle.json`, + `!${namespace}/test/` + ]; + + // Add configured excludes for namespace + allFilterExcludes.forEach((filterExclude) => { + // Allow all excludes (!) and limit re-includes (+) to the component namespace + if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) { + filters.push(filterExclude); + unusedFilterExcludes.delete(filterExclude); + } + }); + + // Exclude other namespaces at the end of filter list to override potential re-includes + // from "excludes" config + allNamespaces.forEach((ns) => { + if (ns !== namespace && ns.startsWith(`${namespace}/`)) { + filters.push(`!${ns}/`); + // Explicitly exclude manifest.json files of subcomponents since the general exclude above this + // comment only applies to the configured default file types, which do not include ".json" + filters.push(`!${ns}/**/manifest.json`); + } + }); + + return { + name: bundleName, + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + sections: [ + { + mode: "preload", + filters: filters, + resolve: false, + resolveConditional: false, + renderer: false + } + ] + }; + }); + + if (unusedFilterExcludes.size > 0) { + unusedFilterExcludes.forEach((filterExclude) => { + log.warn( + `Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` + + `Re-includes must start with a component namespace (${allNamespaces.join(" or ")})` + ); + }); + } + const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion(); + const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0"); + return Promise.all(bundleDefinitions.filter(Boolean).map((bundleDefinition) => { + log.verbose(`Generating ${bundleDefinition.name}...`); + const options = { + bundleDefinition: applyDefaultsToBundleDefinition(bundleDefinition, taskUtil), + bundleOptions: { + ignoreMissingModules: true, + optimize: true + }, + allowStringBundling + }; + if (coreVersion) { + options.targetUi5CoreVersion = coreVersion; + } + return moduleBundler({ + resources, + options + }); + })); + }) + .then((results) => { + const bundles = Array.prototype.concat.apply([], results); + return Promise.all(bundles.map(({bundle, sourceMap}) => { + if (taskUtil) { + taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle); + // Clear tag that might have been set by the minify task, in cases where + // the bundle name is identical to a source file + taskUtil.clearTag(sourceMap, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + return Promise.all([ + workspace.write(bundle), + workspace.write(sourceMap) + ]); + })); + }); +} diff --git a/packages/builder/lib/tasks/bundlers/generateFlexChangesBundle.js b/packages/builder/lib/tasks/bundlers/generateFlexChangesBundle.js new file mode 100644 index 00000000000..5cc43654a31 --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/generateFlexChangesBundle.js @@ -0,0 +1,123 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:bundlers:generateFlexChangesBundle"); +import flexChangesBundler from "../../processors/bundlers/flexChangesBundler.js"; +import semver from "semver"; + +/** + * @public + * @module @ui5/builder/tasks/bundlers/generateFlexChangesBundle + */ + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * Task to create changesBundle.json file containing all changes stored in the /changes folder for easier consumption + * at runtime. + * If a change bundle is created, "sap.ui.fl" is added as a dependency to the manifest.json if not already present - + * if the dependency is already listed but lazy-loaded, lazy loading is disabled. + * If minUI5Version >= 1.73 flexibility-bundle.json will be create. + * If there are control variants and minUI5Version < 1.73 build will break and throw an error. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} [parameters.options] Options + * @param {string} [parameters.options.projectNamespace] Project Namespace + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, taskUtil, options = {}}) { + const namespace = options.projectNamespace; + + // Use the given namespace if available, otherwise use no namespace + // (e.g. in case no manifest.json is present) + let pathPrefix = ""; + if (namespace) { + pathPrefix = `/resources/${namespace}`; + } + + function updateJson(data) { + // ensure the existence of the libs section in the dependencies + data["sap.ui5"] = data["sap.ui5"] || {}; + data["sap.ui5"].dependencies = data["sap.ui5"].dependencies || {}; + const mLibs = data["sap.ui5"].dependencies.libs = data["sap.ui5"].dependencies.libs || {}; + + if (mLibs["sap.ui.fl"]) { + log.verbose("sap.ui.fl found in manifest.json"); + if (mLibs["sap.ui.fl"].lazy) { + log.verbose("sap.ui.fl 'lazy' attribute found in manifest.json, setting it to false..."); + mLibs["sap.ui.fl"].lazy = false; + } + } else { + log.verbose("sap.ui.fl not found in manifest.json, inserting it..."); + mLibs["sap.ui.fl"] = {}; + } + } + + async function updateFLdependency() { + const manifestResource = await workspace.byPath(`${pathPrefix}/manifest.json`); + const manifestContent = JSON.parse(await manifestResource.getString()); + + updateJson(manifestContent); + manifestResource.setString(JSON.stringify(manifestContent, null, "\t")); + + await workspace.write(manifestResource); + } + + async function readManifestMinUI5Version() { + const manifestResource = await workspace.byPath(`${pathPrefix}/manifest.json`); + const manifestContent = JSON.parse(await manifestResource.getString()); + + manifestContent["sap.ui5"] = manifestContent["sap.ui5"] || {}; + manifestContent["sap.ui5"].dependencies = manifestContent["sap.ui5"].dependencies || {}; + if (!Array.isArray(manifestContent["sap.ui5"].dependencies.minUI5Version)) { + manifestContent["sap.ui5"].dependencies.minUI5Version = + // eslint-disable-next-line no-constant-binary-expression + [manifestContent["sap.ui5"].dependencies.minUI5Version] || [""]; + } + return manifestContent["sap.ui5"].dependencies.minUI5Version; + } + + log.verbose("Collecting flexibility changes"); + const allResources = await workspace.byGlob( + `${pathPrefix}/changes/*.{change,variant,ctrl_variant,ctrl_variant_change,ctrl_variant_management_change}`); + if (allResources.length > 0) { + const versionArray = await readManifestMinUI5Version(); + const versions = versionArray.map((version) => semver.coerce(version)); + const versionsAllSuitableForFlexBundle = versions.every((version) => semver.compare(version, "1.73.0") >= 0); + let hasFlexBundleVersion = false; + let flexBundle = {}; + if (versionsAllSuitableForFlexBundle) { + hasFlexBundleVersion = true; + const flexBundleResource = await workspace.byPath(`${pathPrefix}/changes/flexibility-bundle.json`); + if (flexBundleResource) { + flexBundle = JSON.parse(await flexBundleResource.getString()); + } + } + const processedResources = await flexChangesBundler({ + resources: allResources, + options: { + pathPrefix, + hasFlexBundleVersion + }, + existingFlexBundle: flexBundle + }); + await Promise.all(processedResources.map((resource) => { + log.verbose("Writing flexibility changes bundle"); + return workspace.write(resource); + })); + // Add the sap.ui.fl dependency if a bundle has been created + if (processedResources.length > 0) { + await updateFLdependency(); + } + + // Do not write bundled source files to build result + if (taskUtil) { + allResources.forEach((resource) => { + taskUtil.setTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + }); + } + } +} diff --git a/packages/builder/lib/tasks/bundlers/generateLibraryPreload.js b/packages/builder/lib/tasks/bundlers/generateLibraryPreload.js new file mode 100644 index 00000000000..c81f6f081a6 --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/generateLibraryPreload.js @@ -0,0 +1,458 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:bundlers:generateLibraryPreload"); +import moduleBundler from "../../processors/bundlers/moduleBundler.js"; +import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js"; +import {negateFilters} from "../../lbt/resources/ResourceFilterList.js"; +import createModuleNameMapping from "./utils/createModuleNameMapping.js"; + +function getDefaultLibraryPreloadFilters(namespace, excludes) { + const filters = [ + `${namespace}/`, + `${namespace}/**/manifest.json`, + `!${namespace}/**/*-preload.js`, // exclude all bundles + `!${namespace}/designtime/`, + `!${namespace}/**/*.designtime.js`, + `!${namespace}/**/*.support.js` + ]; + + if (Array.isArray(excludes)) { + const allFilterExcludes = negateFilters(excludes); + // Add configured excludes at the end of filter list + allFilterExcludes.forEach((filterExclude) => { + // Allow all excludes (!) and limit re-includes (+) to the library namespace + if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) { + filters.push(filterExclude); + } else { + log.warn(`Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` + + `Re-includes must start with the library's namespace ${namespace}`); + } + }); + } + + return filters; +} + +function getBundleDefinition(namespace, excludes) { + // Note: This configuration is only used when no bundle definition in ui5.yaml exists (see "skipBundles" parameter) + + // TODO: Remove this hardcoded bundle definition once support for relevant versions has ended. + // sap.ui.core ui5.yaml contains a configuration since UI5 1.103.0 (specVersion 2.4) + // so this is still required to build UI5 versions <= 1.102.0 (such as 1.84 and 1.96) + if (namespace === "sap/ui/core") { + return { + name: `${namespace}/library-preload.js`, + sections: [ + { + // exclude the content of sap-ui-core by declaring it as 'provided' + mode: "provided", + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js" + ], + resolve: true + }, + { + mode: "preload", + filters: [ + // Note: Don't pass configured preload excludes for sap.ui.core + // as they are already hardcoded below. + ...getDefaultLibraryPreloadFilters(namespace), + + `!${namespace}/cldr/`, + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + + // include only thirdparty that is very likely to be used + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + + // other excludes (not required for productive scenarios) + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js" + + ], + resolve: false, + resolveConditional: false, + renderer: true + } + ] + }; + } + return { + name: `${namespace}/library-preload.js`, + sections: [ + { + mode: "preload", + filters: getDefaultLibraryPreloadFilters(namespace, excludes), + resolve: false, + resolveConditional: false, + renderer: true + } + ] + }; +} + +function getDesigntimeBundleDefinition(namespace) { + return { + name: `${namespace}/designtime/library-preload.designtime.js`, + sections: [ + { + mode: "preload", + filters: [ + `${namespace}/**/*.designtime.js`, + `${namespace}/designtime/`, + `!${namespace}/**/*-preload.designtime.js`, + `!${namespace}/designtime/**/*.properties`, + `!${namespace}/designtime/**/*.svg`, + `!${namespace}/designtime/**/*.xml` + ], + resolve: false, + resolveConditional: false, + renderer: false + } + ] + }; +} + +function getSupportFilesBundleDefinition(namespace) { + return { + name: `${namespace}/library-preload.support.js`, + sections: [ + { + mode: "preload", + filters: [ + `${namespace}/**/*.support.js`, + `!${namespace}/**/*-preload.support.js` + ], + resolve: false, + resolveConditional: false, + renderer: false + } + ] + }; +} + +function getModuleBundlerOptions(config) { + const moduleBundlerOptions = {}; + + // required in sap-ui-core-nojQuery.js and sap-ui-core-nojQuery-dbg.js + const providedSection = { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ] + }; + + moduleBundlerOptions.bundleOptions = { + optimize: config.preload, + decorateBootstrapModule: config.preload, + addTryCatchRestartWrapper: config.preload + }; + + moduleBundlerOptions.bundleDefinition = getSapUiCoreBunDef(config.name, config.filters, config.preload); + + if (config.provided) { + moduleBundlerOptions.bundleDefinition.sections.unshift(providedSection); + } + + if (config.moduleNameMapping) { + moduleBundlerOptions.moduleNameMapping = config.moduleNameMapping; + } + + return moduleBundlerOptions; +} + +function getSapUiCoreBunDef(name, filters, preload) { + const bundleDefinition = { + name, + sections: [] + }; + + // add raw section + bundleDefinition.sections.push({ + // include all 'raw' modules that are needed for the UI5 loader + mode: "raw", + filters, + resolve: true, // dependencies for raw modules are taken from shims in .library files + sort: true, // topological sort on raw modules is mandatory + declareModules: false + }); + + if (preload) { + // add preload section + bundleDefinition.sections.push({ + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true + }); + } + + // add require section + bundleDefinition.sections.push({ + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ] + }); + + return bundleDefinition; +} + +/** + * @public + * @module @ui5/builder/tasks/bundlers/generateLibraryPreload + */ + +/** + * Task for library bundling. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/project/build/helpers/TaskUtil} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string[]} [parameters.options.skipBundles] Names of bundles that should not be created + * @param {string[]} [parameters.options.excludes=[]] List of modules declared as glob patterns (resource name patterns) + * that should be excluded from the library-preload.js bundle. + * A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk, + * denote an arbitrary number of characters or folder names. + * Re-includes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later + * inclusion overrides an earlier exclusion, and vice versa. + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, taskUtil, options: {skipBundles = [], excludes = [], projectName}}) { + let nonDbgWorkspace = workspace; + if (taskUtil) { + nonDbgWorkspace = taskUtil.resourceFactory.createFilterReader({ + reader: workspace, + callback: function(resource) { + // Remove any debug variants + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.IsDebugVariant); + } + }); + } + const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion(); + const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0"); + const execModuleBundlerIfNeeded = ({options, resources}) => { + if (skipBundles.includes(options.bundleDefinition.name)) { + log.verbose(`Skipping generation of bundle ${options.bundleDefinition.name}`); + return null; + } + if (coreVersion) { + options.targetUi5CoreVersion = coreVersion; + } + options.bundleDefinition = applyDefaultsToBundleDefinition(options.bundleDefinition, taskUtil); + options.allowStringBundling = allowStringBundling; + return moduleBundler({options, resources}); + }; + + return nonDbgWorkspace.byGlob("/**/*.{js,json,xml,html,properties,library,js.map}").then(async (resources) => { + // Find all libraries and create a library-preload.js bundle + + let p = Promise.resolve(); + + // Create core bundles for older versions (<1.97.0) which don't define bundle configuration in the ui5.yaml + // See: https://github.com/SAP/openui5/commit/ff127fd2d009162ea43ad312dec99d759ebc23a0 + if (projectName === "sap.ui.core") { + // Instead of checking the sap.ui.core library version, the specVersion is checked against all versions + // that have been defined for sap.ui.core before the bundle configuration has been introduced. + // This is mainly to have an easier check without version parsing or using semver. + // If no project/specVersion is available, the bundles should also be created to not break potential + // existing use cases without a properly formed/formatted project tree. + if (!taskUtil || taskUtil.getProject().getSpecVersion().lte("2.0")) { + const isEvo = resources.find((resource) => { + return resource.getPath() === "/resources/ui5loader.js"; + }); + + let unoptimizedModuleNameMapping; + let unoptimizedResources = resources; + if (taskUtil) { + const unoptimizedWorkspace = taskUtil.resourceFactory.createFilterReader({ + reader: workspace, + callback: function(resource) { + // Remove any non-debug variants + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.HasDebugVariant); + } + }); + unoptimizedResources = + await unoptimizedWorkspace.byGlob("/**/*.{js,json,xml,html,properties,library,js.map}"); + + unoptimizedModuleNameMapping = createModuleNameMapping({ + resources: unoptimizedResources, + taskUtil + }); + } + + let filters; + if (isEvo) { + filters = ["ui5loader-autoconfig.js"]; + } else { + filters = ["jquery.sap.global.js"]; + } + p = Promise.all([ + execModuleBundlerIfNeeded({ + options: getModuleBundlerOptions({name: "sap-ui-core.js", filters, preload: true}), + resources + }), + execModuleBundlerIfNeeded({ + options: getModuleBundlerOptions({ + name: "sap-ui-core-dbg.js", filters, preload: false, + moduleNameMapping: unoptimizedModuleNameMapping + }), + resources: unoptimizedResources + }), + execModuleBundlerIfNeeded({ + options: getModuleBundlerOptions({ + name: "sap-ui-core-nojQuery.js", filters, preload: true, provided: true + }), + resources + }), + execModuleBundlerIfNeeded({ + options: getModuleBundlerOptions({ + name: "sap-ui-core-nojQuery-dbg.js", filters, preload: false, provided: true, + moduleNameMapping: unoptimizedModuleNameMapping + }), + resources: unoptimizedResources + }), + ]).then((results) => { + const bundles = Array.prototype.concat.apply([], results).filter(Boolean); + return Promise.all(bundles.map(({bundle, sourceMap}) => { + if (taskUtil) { + taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle); + if (sourceMap) { + // Clear tag that might have been set by the minify task, in cases where + // the bundle name is identical to a source file + taskUtil.clearTag(sourceMap, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + } + const writes = [workspace.write(bundle)]; + if (sourceMap) { + writes.push(workspace.write(sourceMap)); + } + return Promise.all(writes); + })); + }); + } + } + + return p.then(() => { + return workspace.byGlob("/resources/**/.library").then((libraryIndicatorResources) => { + if (libraryIndicatorResources.length > 0) { + return libraryIndicatorResources; + } else { + // Fallback to "library.js" as library indicator + log.verbose( + `Could not find a ".library" file for project ${projectName}, ` + + `falling back to "library.js".`); + return workspace.byGlob("/resources/**/library.js"); + } + }).then((libraryIndicatorResources) => { + if (libraryIndicatorResources.length < 1) { + // No library found - nothing to do + log.verbose( + `Could not find a ".library" or "library.js" file for project ${projectName}. ` + + `Skipping library preload bundling.`); + return; + } + + return Promise.all(libraryIndicatorResources.map(async (libraryIndicatorResource) => { + // Determine library namespace from library indicator file path + // ending with either ".library" or "library.js" (see fallback logic above) + // e.g. /resources/sap/foo/.library => sap/foo + // /resources/sap/bar/library.js => sap/bar + const libraryNamespacePattern = /^\/resources\/(.*)\/(?:\.library|library\.js)$/; + const libraryIndicatorPath = libraryIndicatorResource.getPath(); + const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); + if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { + const libraryNamespace = libraryNamespaceMatch[1]; + const results = await Promise.all([ + execModuleBundlerIfNeeded({ + options: { + bundleDefinition: getBundleDefinition(libraryNamespace, excludes), + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }), + execModuleBundlerIfNeeded({ + options: { + bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace), + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources + }), + execModuleBundlerIfNeeded({ + options: { + bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace), + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + } + // Note: Although the bundle uses optimize=false, there is + // no moduleNameMapping needed, as support files are excluded from minification. + }, + resources + }) + ]); + const bundles = Array.prototype.concat.apply([], results).filter(Boolean); + return Promise.all(bundles.map(({bundle, sourceMap} = {}) => { + if (bundle) { + if (taskUtil) { + taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle); + if (sourceMap) { + // Clear tag that might have been set by the minify task, in cases where + // the bundle name is identical to a source file + taskUtil.clearTag(sourceMap, + taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + } + const writes = [workspace.write(bundle)]; + if (sourceMap) { + writes.push(workspace.write(sourceMap)); + } + return Promise.all(writes); + } + })); + } else { + log.verbose( + `Could not determine library namespace from file "${libraryIndicatorPath}" ` + + `for project ${projectName}. Skipping library preload bundling.`); + return Promise.resolve(); + } + })); + }); + }); + }); +} diff --git a/packages/builder/lib/tasks/bundlers/generateStandaloneAppBundle.js b/packages/builder/lib/tasks/bundlers/generateStandaloneAppBundle.js new file mode 100644 index 00000000000..93f5a1f92ef --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/generateStandaloneAppBundle.js @@ -0,0 +1,204 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:bundlers:generateStandaloneAppBundle"); +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; +import moduleBundler from "../../processors/bundlers/moduleBundler.js"; +import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js"; +import createModuleNameMapping from "./utils/createModuleNameMapping.js"; + +function getBundleDefinition(config) { + const bundleDefinition = { + name: config.name, + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + sections: [] + }; + + // add raw section + bundleDefinition.sections.push({ + // include all 'raw' modules that are needed for the UI5 loader + mode: "raw", + filters: config.filters, + resolve: true, // dependencies for raw modules are taken from shims in .library files + sort: true, // topological sort on raw modules is mandatory + declareModules: false + }); + + // preload section is only relevant for sap-ui-custom.js + if (config.preloadSection) { + bundleDefinition.sections.push({ + mode: "preload", + filters: [ + `${config.namespace || ""}/`, + `${config.namespace || ""}/**/manifest.json`, + `${config.namespace || ""}/changes/changes-bundle.json`, + `${config.namespace || ""}/changes/flexibility-bundle.json`, + `!${config.namespace || ""}/test/`, + "sap/ui/core/Core.js" + ], + resolve: true, + resolveConditional: true, + renderer: true + }); + } + + bundleDefinition.sections.push({ + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ] + }); + + return bundleDefinition; +} + +/** + * @public + * @module @ui5/builder/tasks/bundlers/generateStandaloneAppBundle + */ + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * Task for bundling standalone applications. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string} [parameters.options.projectNamespace] Project namespace + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, dependencies, taskUtil, options}) { + const {projectName} = options; + const namespace = options.projectNamespace; + const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion(); + + if (!namespace) { + log.warn(`Namespace of project ${projectName} is not known. Self contained bundling is currently ` + + `unable to generate complete bundles for such projects.`); + } + + const combo = new ReaderCollectionPrioritized({ + name: `generateStandaloneAppBundle - prioritize workspace over dependencies: ${projectName}`, + readers: [workspace, dependencies] + }); + + let resourceReader = combo; + if (taskUtil) { + // Omit -dbg files + resourceReader = await taskUtil.resourceFactory.createFilterReader({ + reader: combo, + callback: function(resource) { + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.IsDebugVariant); + } + }); + } + const resources = await resourceReader.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}"); + + const isEvo = resources.find((resource) => { + return resource.getPath() === "/resources/ui5loader.js"; + }); + let filters; + if (isEvo) { + filters = ["ui5loader-autoconfig.js"]; + } else { + filters = ["jquery.sap.global.js"]; + } + + let unoptimizedModuleNameMapping; + let unoptimizedResources = resources; + if (taskUtil) { + const unoptimizedResourceReader = await taskUtil.resourceFactory.createFilterReader({ + reader: combo, + callback: function(resource) { + // Remove any non-debug variants + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.HasDebugVariant); + } + }); + + unoptimizedResources = await unoptimizedResourceReader + .byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}"); + + unoptimizedModuleNameMapping = createModuleNameMapping({ + resources: unoptimizedResources, + taskUtil + }); + } + + const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0"); + const bundleOptions = { + bundleDefinition: applyDefaultsToBundleDefinition( + getBundleDefinition({ + name: "sap-ui-custom.js", + filters, + namespace, + preloadSection: true, + }), + taskUtil + ), + allowStringBundling + }; + + const bundleDbgOptions = { + bundleDefinition: applyDefaultsToBundleDefinition( + getBundleDefinition({ + name: "sap-ui-custom-dbg.js", + filters, + namespace, + }), + taskUtil + ), + bundleOptions: { + optimize: false, + }, + moduleNameMapping: unoptimizedModuleNameMapping, + allowStringBundling + }; + + if (coreVersion) { + bundleOptions.targetUi5CoreVersion = coreVersion; + bundleDbgOptions.targetUi5CoreVersion = coreVersion; + } + + await Promise.all([ + moduleBundler({ + resources, + options: bundleOptions + }), + moduleBundler({ + resources: unoptimizedResources, + options: bundleDbgOptions + }) + ]).then((results) => { + const bundles = Array.prototype.concat.apply([], results); + return Promise.all(bundles.map(({bundle, sourceMap}) => { + if (taskUtil) { + taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle); + if (sourceMap) { + // Clear tag that might have been set by the minify task, in cases where + // the bundle name is identical to a source file + taskUtil.clearTag(sourceMap, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + } + const writes = [workspace.write(bundle)]; + if (sourceMap) { + writes.push(workspace.write(sourceMap)); + } + return Promise.all(writes); + })); + }); +} diff --git a/packages/builder/lib/tasks/bundlers/utils/applyDefaultsToBundleDefinition.js b/packages/builder/lib/tasks/bundlers/utils/applyDefaultsToBundleDefinition.js new file mode 100644 index 00000000000..96d4db00f3c --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/utils/applyDefaultsToBundleDefinition.js @@ -0,0 +1,35 @@ +/** + * Applies default values to bundleDefinitions + * + * @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinition} bundleDefinition Module + * bundle definition + * @param {@ui5/project/build/helpers/TaskUtil|object} [taskUtil] TaskUtil + * + * @returns {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleDefinition} + */ +export function applyDefaultsToBundleDefinition(bundleDefinition, taskUtil) { + bundleDefinition.sections = bundleDefinition?.sections?.map((section) => { + const defaultValues = { + resolve: false, + resolveConditional: false, + renderer: false, + sort: true, + declareRawModules: false, + }; + + // Since specVersion: 4.0 "require" section starts loading asynchronously. + // If specVersion cannot be determined, the latest spec is taken into account. + // This is a breaking change in specVersion: 4.0 + if (section.mode === "require") { + // Builder.js already treats missing async flag as truthy value and builds asynchronously by default + + if (taskUtil && taskUtil.getProject().getSpecVersion().lt("4.0")) { + defaultValues.async = false; + } + } + + return Object.assign(defaultValues, section); + }); + + return bundleDefinition; +} diff --git a/packages/builder/lib/tasks/bundlers/utils/createModuleNameMapping.js b/packages/builder/lib/tasks/bundlers/utils/createModuleNameMapping.js new file mode 100644 index 00000000000..9a950051992 --- /dev/null +++ b/packages/builder/lib/tasks/bundlers/utils/createModuleNameMapping.js @@ -0,0 +1,31 @@ +import {getNonDebugName} from "../../../lbt/utils/ModuleName.js"; + +/** + * For "unoptimized" bundles, the non-debug files have already been filtered out above. + * Now we need to create a mapping from the debug-variant resource path to the respective module + * name, which is basically the non-debug resource path, minus the "/resources/"" prefix. + * This mapping overwrites internal logic of the LocatorResourcePool which would otherwise determine + * the module name from the resource path, which would contain "-dbg" in this case. That would be + * incorrect since debug-variants should still keep the original module name. + * + * @private + * @param {object} parameters Parameters + * @param {@ui5/fs/Resource[]} parameters.resources List of resources + * @param {@ui5/project/build/helpers/TaskUtil|object} parameters.taskUtil TaskUtil + * @returns {object} Module name mapping + */ +export default function({resources, taskUtil}) { + const moduleNameMapping = Object.create(null); + for (let i = resources.length - 1; i >= 0; i--) { + const resource = resources[i]; + const resourcePath = resource.getPath(); + if (resourcePath.endsWith(".js") && taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.IsDebugVariant)) { + const nonDbgPath = getNonDebugName(resourcePath); + if (!nonDbgPath) { + throw new Error(`Failed to resolve non-debug name for ${resourcePath}`); + } + moduleNameMapping[resourcePath] = nonDbgPath.slice("/resources/".length); + } + } + return moduleNameMapping; +} diff --git a/packages/builder/lib/tasks/enhanceManifest.js b/packages/builder/lib/tasks/enhanceManifest.js new file mode 100644 index 00000000000..e643f924f71 --- /dev/null +++ b/packages/builder/lib/tasks/enhanceManifest.js @@ -0,0 +1,36 @@ +import manifestEnhancer from "../processors/manifestEnhancer.js"; +import fsInterface from "@ui5/fs/fsInterface"; + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * @public + * @module @ui5/builder/tasks/enhanceManifest + */ +/** + * Task for transforming the manifest.json file. + * Adds missing information based on the available project resources, + * for example the locales supported by the present i18n resources. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectNamespace Namespace of the application + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, options}) { + const {projectNamespace} = options; + + // Note: all "manifest.json" files in the given namespace + const resources = await workspace.byGlob(`/resources/${projectNamespace}/**/manifest.json`); + + const processedResources = await manifestEnhancer({ + resources, + fs: fsInterface(workspace), + }); + + await Promise.all(processedResources.map((resource) => workspace.write(resource))); +} diff --git a/packages/builder/lib/tasks/escapeNonAsciiCharacters.js b/packages/builder/lib/tasks/escapeNonAsciiCharacters.js new file mode 100644 index 00000000000..53cb3e8d9f3 --- /dev/null +++ b/packages/builder/lib/tasks/escapeNonAsciiCharacters.js @@ -0,0 +1,37 @@ +import nonAsciiEscaper from "../processors/nonAsciiEscaper.js"; + +/** + * @public + * @module @ui5/builder/tasks/escapeNonAsciiCharacters + */ + +/** + * Task to escape non ascii characters in properties files resources. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Glob pattern to locate the files to be processed + * @param {string} parameters.options.encoding source file encoding either "UTF-8" or "ISO-8859-1" + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, options: {pattern, encoding}}) { + if (!encoding) { + throw new Error("[escapeNonAsciiCharacters] Mandatory option 'encoding' not provided"); + } + + const allResources = await workspace.byGlob(pattern); + + const processedResources = await nonAsciiEscaper({ + resources: allResources, + options: { + encoding: nonAsciiEscaper.getEncodingFromAlias(encoding) + } + }); + + await Promise.all(processedResources.map((resource) => workspace.write(resource))); +} diff --git a/packages/builder/lib/tasks/generateCachebusterInfo.js b/packages/builder/lib/tasks/generateCachebusterInfo.js new file mode 100644 index 00000000000..76610f8cc8f --- /dev/null +++ b/packages/builder/lib/tasks/generateCachebusterInfo.js @@ -0,0 +1,79 @@ +import crypto from "node:crypto"; +import {createResource} from "@ui5/fs/resourceFactory"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:generateCachebusterInfo"); + +/** + * @public + * @module @ui5/builder/tasks/generateCachebusterInfo + */ + +async function signByTime(resource) { + return resource.getStatInfo().mtime.getTime(); +} + +async function signByHash(resource) { + const hasher = crypto.createHash("sha1"); + const buffer = await resource.getBuffer(); + + hasher.update(buffer.toString("binary")); + return hasher.digest("hex"); +} + +function getSigner(type) { + type = type || "time"; + + switch (type) { + case "time": + return signByTime; + case "hash": + return signByHash; + + default: + throw new Error(`Invalid signature type: '${type}'. Valid ones are: 'time' or 'hash'`); + } +} + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * Task to generate the application cachebuster info file. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectNamespace Namespace of the application + * @param {string} [parameters.options.signatureType='time'] Type of signature to be used ('time' or 'hash') + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default function({workspace, options}) { + const {signatureType} = options; + const namespace = options.projectNamespace; + + const basePath = `/resources/${namespace}/`; + return workspace.byGlob(`/resources/${namespace}/**/*`) + .then(async (resources) => { + const cachebusterInfo = Object.create(null); + const signer = getSigner(signatureType); + + await Promise.all(resources.map(async (resource) => { + let resourcePath = resource.getPath(); + if (!resourcePath.startsWith(basePath)) { + log.verbose( + `Ignoring resource with path ${resourcePath} since it is not based on path ${basePath}`); + return; + } + // Remove base path. Absolute paths are not allowed in cachebuster info + resourcePath = resourcePath.replace(basePath, ""); + cachebusterInfo[resourcePath] = await signer(resource); + })); + const cachebusterInfoResource = createResource({ + path: `/resources/${namespace}/sap-ui-cachebuster-info.json`, + string: JSON.stringify(cachebusterInfo, null, 2) + }); + return workspace.write(cachebusterInfoResource); + }); +} diff --git a/packages/builder/lib/tasks/generateLibraryManifest.js b/packages/builder/lib/tasks/generateLibraryManifest.js new file mode 100644 index 00000000000..44c464e322b --- /dev/null +++ b/packages/builder/lib/tasks/generateLibraryManifest.js @@ -0,0 +1,73 @@ + +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:generateLibraryManifest"); +import manifestCreator from "../processors/manifestCreator.js"; + +/** + * @public + * @module @ui5/builder/tasks/generateLibraryManifest + */ + +/** + * Task for creating a library manifest.json from its .library file. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default function({workspace, taskUtil, options: {projectName}}) { + // Note: + // *.library files are needed to identify libraries + // *.json files are needed to avoid overwriting them + // *.js files are needed to identify nested components + // *.less, *.css, *.theming and *.theme files are needed to identify supported themes + // *.properties to identify existence of i18n bundles (e.g. messagebundle.properties) + return workspace.byGlob("/resources/**/*.{js,json,library,less,css,theming,theme,properties}").then((resources) => { + // Find all libraries and create a manifest.json file + return workspace.byGlob("/resources/**/.library").then((libraryIndicatorResources) => { + if (libraryIndicatorResources.length < 1) { + // No library found - nothing to do + log.verbose(`Could not find a ".library" file for project ${projectName}. ` + + `Skipping library manifest generation.`); + return; + } + + return Promise.all(libraryIndicatorResources.map((libraryIndicatorResource) => { + // Determine library namespace from library indicator file path + // ending with ".library" + // e.g. /resources/sap/foo/.library => sap/foo + const libraryNamespacePattern = /^\/resources\/(.*)\/\.library$/; + const libraryIndicatorPath = libraryIndicatorResource.getPath(); + const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); + if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { + const libraryNamespace = libraryNamespaceMatch[1]; + return manifestCreator({ + libraryResource: libraryIndicatorResource, + namespace: libraryNamespace, + resources, + getProjectVersion: (projectName) => { + return taskUtil?.getProject(projectName)?.getVersion(); + }, + options: { + } + }).then((manifest) => { + if (manifest) { + return workspace.write(manifest); + } + }); + } else { + log.verbose(`Could not determine library namespace from file "${libraryIndicatorPath}" ` + + `for project ${projectName}. Skipping library manifest generation.`); + return Promise.resolve(); + } + })); + }); + }); +} diff --git a/packages/builder/lib/tasks/generateResourcesJson.js b/packages/builder/lib/tasks/generateResourcesJson.js new file mode 100644 index 00000000000..fae7b2c88c4 --- /dev/null +++ b/packages/builder/lib/tasks/generateResourcesJson.js @@ -0,0 +1,134 @@ + +import resourceListCreator from "../processors/resourceListCreator.js"; + +const DEFAULT_EXCLUDES = [ + /* + * exclude mac metadata files + */ + "!**/.DS_Store", + /* + * sap-ui-version.json is not part of the resources + */ + "!/resources/sap-ui-version.json" +]; + +function getCreatorOptions(projectName) { + const creatorOptions = {}; + // TODO: Move configuration into ui5.yaml + if ( projectName === "sap.ui.core" ) { + Object.assign(creatorOptions, { + externalResources: { + "sap/ui/core": [ + "*", + "sap/base/", + "sap/ui/" + ] + } + }); + } else if ( projectName === "sap.ui.integration" ) { + Object.assign(creatorOptions, { + externalResources: { + "sap/ui/integration": [ + "sap-ui-integration*.js", + ] + } + }); + } + return creatorOptions; +} + +/** + * @public + * @module @ui5/builder/tasks/generateResourcesJson + */ + +/** + * Task for creating a resources.json file, describing all productive build resources. + * + *

    + * The detailed structure can be found in the documentation: + * {@link https://sdk.openui5.org/topic/adcbcf8b50924556ab3f321fcd9353ea} + *

    + * + *

    + * Not supported in combination with task {@link @ui5/builder/tasks/bundlers/generateStandaloneAppBundle}. + * Therefore it is also not supported in combination with self-contained build. + *

    + * + * @example sample resources.json + * const resourcesJson = { + * "_version": "1.1.0", + * "resources": [ + * { + * "name": "Component-preload.js", + * "module": "application/mine/Component-preload.js", + * "size": 3746, + * "merged": true, + * "included": [ + * "application/mine/Component.js", + * "application/mine/changes/coding/MyExtension.js", + * "application/mine/changes/flexibility-bundle.json", + * "application/mine/changes/fragments/MyFragment.fragment.xml", + * "application/mine/manifest.json" + * ] + * }, + * { + * "name": "resources.json", + * "size": 1870 + * }, + * { + * "name": "rules/Button-dbg.support.js", + * "module": "application/mine/rules/Button.support.js", + * "size": 211, + * "format": "raw", + * "isDebug": true, + * "required": [ + * "application/mine/library.js", + * "sap/ui/core/Control.js" + * ], + * "condRequired": [ + * "application/mine/changeHandler/SplitButton.js", + * "sap/ui/core/format/DateFormat.js" + * ], + * "dynRequired": true, + * "support": true + * } + * ] + * }; + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, dependencies, taskUtil, options: {projectName}}) { + let resources = await workspace.byGlob(["/resources/**/*"].concat(DEFAULT_EXCLUDES)); + let dependencyResources = + await dependencies.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}"); + + if (taskUtil) { + // Filter out resources that will be omitted from the build results + resources = resources.filter((resource) => { + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + }); + dependencyResources = dependencyResources.filter((resource) => { + return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + }); + } + + const resourceLists = await resourceListCreator({ + resources, + dependencyResources, + options: getCreatorOptions(projectName), + }); + await Promise.all( + resourceLists.map((resourceList) => workspace.write(resourceList)) + ); +} diff --git a/packages/builder/lib/tasks/generateThemeDesignerResources.js b/packages/builder/lib/tasks/generateThemeDesignerResources.js new file mode 100644 index 00000000000..df1fe466278 --- /dev/null +++ b/packages/builder/lib/tasks/generateThemeDesignerResources.js @@ -0,0 +1,317 @@ +import posixPath from "node:path/posix"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:generateThemeDesignerResources"); +import libraryLessGenerator from "../processors/libraryLessGenerator.js"; +import {updateLibraryDotTheming} from "./utils/dotTheming.js"; +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; +import Resource from "@ui5/fs/Resource"; +import fsInterface from "@ui5/fs/fsInterface"; + +/** + * Returns a relative path from the given themeFolder to the root namespace. + * + * When combining the given themeFolder with the returned relative path it + * resolves to "/resources/". However the "/resources/" part is not important + * here as it doesn't exist within the theming engine environment where the + * UI5 resources are part of a "UI5" folder (e.g. "UI5/sap/ui/core/") that + * is next to a "Base" folder. + * + * @example + * getPathToRoot("/resources/sap/ui/foo/themes/base") + * > "../../../../../" + * + * @param {string} themeFolder Virtual path including /resources/ + * @returns {string} Relative path to root namespace + */ +function getPathToRoot(themeFolder) { + // -2 for initial "/"" and "resources/" + return "../".repeat(themeFolder.split("/").length - 2); +} + +/** + * Generates an less import statement for the given filePath + * + * @param {string} filePath The path to the desired file + * @returns {string} The less import statement + */ +function lessImport(filePath) { + return `@import "${filePath}";\n`; +} + +function generateLibraryDotTheming({namespace, version, hasThemes}) { + const dotTheming = { + sEntity: "Library", + sId: namespace, + sVersion: version + }; + + // Note that with sap.ui.core version 1.127.0 the .theming file has been put into + // the library sources so that "aFiles" can be maintained from there. + // The below configuration is still needed for older versions of sap.ui.core which do not + // contain the file. + if (namespace === "sap/ui/core") { + dotTheming.aFiles = [ + "library", + "global", // Additional entry compared to UI5 root .theming + "css_variables", + ]; + } + if (!hasThemes) { + // Set ignore flag when there are no themes at all + // This is important in case a library used to contain themes that have been removed + // in a later version of the library. + dotTheming.bIgnore = true; + } + + return new Resource({ + path: `/resources/${namespace}/.theming`, + string: JSON.stringify(dotTheming, null, 2) + }); +} + +async function generateThemeDotTheming({workspace, combo, themeFolder}) { + const themeName = posixPath.basename(themeFolder); + const libraryMatchPattern = /^\/resources\/(.*)\/themes\/[^/]*$/i; + const libraryMatch = libraryMatchPattern.exec(themeFolder); + let libraryName; + if (libraryMatch) { + libraryName = libraryMatch[1].replace(/\//g, "."); + } else { + throw new Error(`Failed to extract library name from theme folder path: ${themeFolder}`); + } + + const dotThemingTargetPath = posixPath.join(themeFolder, ".theming"); + if (libraryName === "sap.ui.core") { + // sap.ui.core should always have a .theming file for all themes + + if (await workspace.byPath(dotThemingTargetPath)) { + // .theming file present, skip further processing + return; + } else { + throw new Error(`.theming file for theme ${themeName} missing in sap.ui.core library source`); + } + } + + let newDotThemingResource; + const coreDotThemingResource = await combo.byPath(`/resources/sap/ui/core/themes/${themeName}/.theming`); + + if (coreDotThemingResource) { + // Copy .theming file from core + newDotThemingResource = await coreDotThemingResource.clone(); + newDotThemingResource.setPath(dotThemingTargetPath); + } else { + // No core .theming file found for this theme => Generate a .theming file + const dotTheming = { + sEntity: "Theme", + sId: themeName, + sVendor: "SAP" + }; + + if (themeName !== "base") { + dotTheming.oExtends = "base"; + } + + newDotThemingResource = new Resource({ + path: dotThemingTargetPath, + string: JSON.stringify(dotTheming, null, 2) + }); + } + return newDotThemingResource; +} + +async function createCssVariablesLessResource({workspace, combo, themeFolder}) { + const pathToRoot = getPathToRoot(themeFolder); + const cssVariablesSourceLessFile = "css_variables.source.less"; + const cssVariablesLessFile = "css_variables.less"; + + // posix as it is a virtual path (separated with /) + const themeName = posixPath.basename(themeFolder); + // The "base" theme of the baseLib is called "baseTheme" + const baseLibThemeName = themeName === "base" ? "baseTheme" : themeName; + + // Some themes do not have a base.less file (e.g. sap_hcb) + const hasBaseLess = !!(await combo.byPath(`/resources/sap/ui/core/themes/${themeName}/base.less`)); + + let cssVariablesLess = +`/* NOTE: This file was generated as an optimized version of "${cssVariablesSourceLessFile}" \ +for the Theme Designer. */\n\n`; + + if (themeName !== "base") { + const cssVariablesSourceLessResource = await workspace.byPath( + posixPath.join(themeFolder, cssVariablesSourceLessFile) + ); + + if (!cssVariablesSourceLessResource) { + throw new Error(`Could not find file "${cssVariablesSourceLessFile}" in theme "${themeFolder}"`); + } + + const cssVariablesSourceLess = await cssVariablesSourceLessResource.getString(); + + cssVariablesLess += lessImport(`../base/${cssVariablesLessFile}`); + cssVariablesLess += ` +/* START "${cssVariablesSourceLessFile}" */ +${cssVariablesSourceLess} +/* END "${cssVariablesSourceLessFile}" */ + +`; + } + + if (hasBaseLess) { + cssVariablesLess += lessImport(`${pathToRoot}../Base/baseLib/${baseLibThemeName}/base.less`); + } + cssVariablesLess += lessImport(`${pathToRoot}sap/ui/core/themes/${themeName}/global.less`); + + return new Resource({ + path: posixPath.join(themeFolder, cssVariablesLessFile), + string: cssVariablesLess + }); +} + +async function generateCssVariablesLess({workspace, combo, namespace}) { + let cssVariablesSourceLessResourcePattern; + if (namespace) { + // In case of a library only check for themes directly below the namespace + cssVariablesSourceLessResourcePattern = `/resources/${namespace}/themes/*/css_variables.source.less`; + } else { + // In case of a theme-library check for all "themes" + cssVariablesSourceLessResourcePattern = `/resources/**/themes/*/css_variables.source.less`; + } + + const cssVariablesSourceLessResource = await workspace.byGlob(cssVariablesSourceLessResourcePattern); + + const hasCssVariables = cssVariablesSourceLessResource.length > 0; + + if (hasCssVariables) { + await Promise.all( + cssVariablesSourceLessResource.map(async (cssVariableSourceLess) => { + const themeFolder = posixPath.dirname(cssVariableSourceLess.getPath()); + log.verbose(`Generating css_variables.less for theme ${themeFolder}`); + const r = await createCssVariablesLessResource({ + workspace, combo, themeFolder + }); + return await workspace.write(r); + }) + ); + } +} + +/** + * @public + * @module @ui5/builder/tasks/generateThemeDesignerResources + */ + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * Generates resources required for integration with the SAP Theme Designer. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.version Project version + * @param {string} [parameters.options.projectNamespace] If the project is of type library, + * provide its namespace. + * Omit for type theme-library + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, dependencies, options}) { + const {projectName, version} = options; + const namespace = options.projectNamespace; + + // Skip sap.ui.documentation since it is not intended to be available in SAP Theme Designer to create custom themes + if (namespace === "sap/ui/documentation") { + return; + } + + let librarySourceLessPattern; + if (namespace) { + // In case of a library only check for themes directly below the namespace + librarySourceLessPattern = `/resources/${namespace}/themes/*/library.source.less`; + } else { + // In case of a theme-library check for all "themes" + librarySourceLessPattern = `/resources/**/themes/*/library.source.less`; + } + + const librarySourceLessResources = await workspace.byGlob(librarySourceLessPattern); + + const hasThemes = librarySourceLessResources.length > 0; + + // library .theming file + // Only for type "library". Type "theme-library" does not provide a namespace + // Also needs to be created in case a library does not have any themes (see bIgnore flag) + if (namespace) { + let libraryDotThemingResource; + + // Do not generate a .theming file for the sap.ui.core library + if (namespace === "sap/ui/core") { + // Check if the .theming file already exists + libraryDotThemingResource = await workspace.byPath(`/resources/${namespace}/.theming`); + if (libraryDotThemingResource) { + // Update the existing .theming resource + log.verbose(`Updating .theming for namespace ${namespace}`); + await updateLibraryDotTheming({ + resource: libraryDotThemingResource, + namespace, + version, + hasThemes + }); + } + } + + if (!libraryDotThemingResource) { + log.verbose(`Generating .theming for namespace ${namespace}`); + libraryDotThemingResource = generateLibraryDotTheming({ + namespace, + version, + hasThemes + }); + } + + await workspace.write(libraryDotThemingResource); + } + + if (!hasThemes) { + // Skip further processing as there are no themes + return; + } + + const combo = new ReaderCollectionPrioritized({ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: ${projectName}`, + readers: [workspace, dependencies] + }); + + // theme .theming files + const themeDotThemingFiles = await Promise.all( + librarySourceLessResources.map((librarySourceLess) => { + const themeFolder = posixPath.dirname(librarySourceLess.getPath()); + log.verbose(`Generating .theming for theme ${themeFolder}`); + return generateThemeDotTheming({ + workspace, combo, themeFolder + }); + }) + ); + await Promise.all( + themeDotThemingFiles.map(async (resource) => { + if (resource) { + await workspace.write(resource); + } + }) + ); + + // library.less files + const libraryLessResources = await libraryLessGenerator({ + resources: librarySourceLessResources, + fs: fsInterface(combo), + }); + await Promise.all( + libraryLessResources.map((resource) => workspace.write(resource)) + ); + + // css_variables.less + await generateCssVariablesLess({workspace, combo, namespace}); +} diff --git a/packages/builder/lib/tasks/generateVersionInfo.js b/packages/builder/lib/tasks/generateVersionInfo.js new file mode 100644 index 00000000000..8cb2f8f6cfb --- /dev/null +++ b/packages/builder/lib/tasks/generateVersionInfo.js @@ -0,0 +1,58 @@ +import versionInfoGenerator from "../processors/versionInfoGenerator.js"; + +const MANIFEST_JSON = "manifest.json"; + +/** + * @public + * @module @ui5/builder/tasks/generateVersionInfo + */ + +/** + * Task to create sap-ui-version.json + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Glob pattern for .library resources + * @param {object} parameters.options.rootProject DuplexCollection to read and write files + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async ({workspace, dependencies, options: {rootProject, pattern}}) => { + let resources = await dependencies.byGlob(pattern); + + resources = resources.filter((res) => res.getProject()?.getType() === "library"); + + const libraryInfosPromises = resources.map((dotLibResource) => { + const namespace = dotLibResource.getProject().getNamespace(); + // pass all required resources to the processor + // the processor will then filter + return dependencies.byGlob(`/resources/${namespace}/**/${MANIFEST_JSON}`).then((manifestResources) => { + const libraryManifest = manifestResources.find((manifestResource) => { + return manifestResource.getPath() === `/resources/${namespace}/${MANIFEST_JSON}`; + }); + const embeddedManifests = + manifestResources.filter((manifestResource) => manifestResource !== libraryManifest); + return { + libraryManifest, + embeddedManifests, + name: dotLibResource.getProject().getName(), + version: dotLibResource.getProject().getVersion() + }; + }); + }); + const libraryInfos = await Promise.all(libraryInfosPromises); + + const [versionInfoResource] = await versionInfoGenerator({ + options: { + rootProjectName: rootProject.getName(), + rootProjectVersion: rootProject.getVersion(), + libraryInfos + } + }); + return workspace.write(versionInfoResource); +}; diff --git a/packages/builder/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/packages/builder/lib/tasks/jsdoc/executeJsdocSdkTransformation.js new file mode 100644 index 00000000000..595b52173c6 --- /dev/null +++ b/packages/builder/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -0,0 +1,84 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:jsdoc:executeJsdocSdkTransformation"); +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; +import fsInterface from "@ui5/fs/fsInterface"; +import sdkTransformer from "../../processors/jsdoc/sdkTransformer.js"; + +/** + * @public + * @module @ui5/builder/tasks/jsdoc/executeJsdocSdkTransformation + */ + +/** + * Task to transform the api.json file as created by the + * [generateJsdoc]{@link @ui5/builder/tasks/jsdoc/generateJsdoc} task into a pre-processed api.json + * file suitable for the SDK. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {object} parameters.options Options + * @param {string|Array} parameters.options.dotLibraryPattern Pattern to locate the .library resource to be processed + * @param {string} parameters.options.projectName Project name + * @returns {Promise} Promise resolving with undefined once data has been written + */ +const executeJsdocSdkTransformation = async function( + {workspace, dependencies, options: {projectName, dotLibraryPattern}} = {} +) { + if (!projectName || !dotLibraryPattern) { + throw new Error("[executeJsdocSdkTransformation]: One or more mandatory options not provided"); + } + + const [apiJsons, dotLibraries, depApiJsons] = await Promise.all([ + workspace.byGlob("/test-resources/**/designtime/api.json"), + workspace.byGlob(dotLibraryPattern), + dependencies.byGlob("/test-resources/**/designtime/api.json") + ]); + if (!apiJsons.length) { + log.info(`Failed to locate api.json resource for project ${projectName}. ` + + `Skipping SDK Transformation...`); + return; + } else if (apiJsons.length > 1) { + throw new Error(`[executeJsdocSdkTransformation]: Found more than one api.json resources for project ` + + `${projectName}.`); + } + if (!dotLibraries.length) { + throw new Error(`[executeJsdocSdkTransformation]: Failed to locate .library resource for project ` + + `${projectName}.`); + } else if (dotLibraries.length > 1) { + throw new Error(`[executeJsdocSdkTransformation]: Found more than one .library resources for project ` + + `${projectName}.`); + } + + const combo = new ReaderCollectionPrioritized({ + name: `executeJsdocSdkTransformation - custom workspace + dependencies FS: ${projectName}`, + readers: [workspace, dependencies] + }); + + const apiJsonPath = apiJsons[0].getPath(); + const dotLibraryPath = dotLibraries[0].getPath(); + const dependencyApiJsonPaths = depApiJsons.map((res) => { + return res.getPath(); + }); + + // Target path is typically "/test-resources/${options.namespace}/designtime/apiref/api.json" + const targetApiJsonPath = apiJsonPath.replace(/\/api\.json$/i, "/apiref/api.json"); + + const createdResources = await sdkTransformer({ + apiJsonPath, + dotLibraryPath, + dependencyApiJsonPaths, + targetApiJsonPath, + fs: fsInterface(combo) + }); + + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); +}; + +export default executeJsdocSdkTransformation; diff --git a/packages/builder/lib/tasks/jsdoc/generateApiIndex.js b/packages/builder/lib/tasks/jsdoc/generateApiIndex.js new file mode 100644 index 00000000000..da742792cdf --- /dev/null +++ b/packages/builder/lib/tasks/jsdoc/generateApiIndex.js @@ -0,0 +1,56 @@ +import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized"; +import fsInterface from "@ui5/fs/fsInterface"; +import apiIndexGenerator from "../../processors/jsdoc/apiIndexGenerator.js"; + +/** + * @public + * @module @ui5/builder/tasks/jsdoc/generateApiIndex + */ + +/** + * Compiles an api-index.json resource from all available api.json resources as created by the + * [executeJsdocSdkTransformation]{@link @ui5/builder/tasks/jsdoc/executeJsdocSdkTransformation} task. + * The resulting api-index.json resource is mainly to be used in the SDK. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({ + workspace, + dependencies, + options: {projectName} +}) { + const combo = new ReaderCollectionPrioritized({ + name: `generateApiIndex - workspace + dependencies: ${projectName}`, + readers: [workspace, dependencies] + }); + + const versionInfoPath = "/resources/sap-ui-version.json"; + const testResourcesRootPath = "/test-resources"; + const targetApiIndexPath = "/docs/api/api-index.json"; + const targetApiIndexDeprecatedPath = "/docs/api/api-index-deprecated.json"; + const targetApiIndexExperimentalPath = "/docs/api/api-index-experimental.json"; + const targetApiIndexSincePath = "/docs/api/api-index-since.json"; + + const createdResources = await apiIndexGenerator({ + versionInfoPath, + testResourcesRootPath, + targetApiIndexPath, + targetApiIndexDeprecatedPath, + targetApiIndexExperimentalPath, + targetApiIndexSincePath, + fs: fsInterface(combo), + }); + + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); +} diff --git a/packages/builder/lib/tasks/jsdoc/generateJsdoc.js b/packages/builder/lib/tasks/jsdoc/generateJsdoc.js new file mode 100644 index 00000000000..938231b0603 --- /dev/null +++ b/packages/builder/lib/tasks/jsdoc/generateJsdoc.js @@ -0,0 +1,195 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:jsdoc:generateJsdoc"); +import path from "node:path"; +import os from "node:os"; +import fs from "graceful-fs"; +import {mkdirp, rmrf} from "../../utils/fs.js"; +import {promisify} from "node:util"; +const mkdtemp = promisify(fs.mkdtemp); +import jsdocGenerator from "../../processors/jsdoc/jsdocGenerator.js"; +import {createAdapter} from "@ui5/fs/resourceFactory"; + +/** + * @public + * @module @ui5/builder/tasks/jsdoc/generateJsdoc + */ + +/** + * + * @public + * @typedef {object} GenerateJsdocOptions + * @property {string|string[]} pattern Pattern to locate the files to be processed + * @property {string} projectName Project name + * @property {string} namespace Namespace to build (e.g. some/project/name) + * @property {string} version Project version + */ + +/** + * Task to execute a JSDoc build for UI5 projects + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {module:@ui5/builder/tasks/jsdoc/generateJsdoc~GenerateJsdocOptions} parameters.options Options + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function generateJsdoc({ + taskUtil, + workspace, + dependencies, + options = {} +}) { + const {projectName, namespace, version, pattern} = + /** @type {module:@ui5/builder/tasks/jsdoc/generateJsdoc~GenerateJsdocOptions} */ (options); + + if (!projectName || !namespace || !version || !pattern) { + throw new Error("[generateJsdoc]: One or more mandatory options not provided"); + } + + const {sourcePath: resourcePath, targetPath, tmpPath, cleanup} = + await utils.createTmpDirs(projectName); + + taskUtil?.registerCleanupTask(cleanup); + + const [writtenResourcesCount] = await Promise.all([ + utils.writeResourcesToDir({ + workspace, + pattern, + targetPath: resourcePath + }), + utils.writeDependencyApisToDir({ + dependencies, + targetPath: path.join(tmpPath, "dependency-apis") + }) + ]); + + if (writtenResourcesCount === 0) { + log.info(`Failed to find any input resources for project ${projectName} using pattern ` + + `${pattern}. Skipping JSDoc generation...`); + return; + } + + const createdResources = await jsdocGenerator({ + sourcePath: resourcePath, + targetPath, + tmpPath, + options: { + projectName, + namespace, + version, + variants: ["apijson"] + } + }); + + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); +} + +const utils = { + /** + * Create temporary directories for JSDoc generation processor + * + * @private + * @param {string} projectName Project name used for naming the temporary working directory + * @returns {Promise} Promise resolving with sourcePath, targetPath and tmpPath strings + */ + createTmpDirs: async function(projectName) { + const tmpDirPath = await utils.createTmpDir(projectName); + + const sourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below + await mkdirp(sourcePath); + const targetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself + await mkdirp(targetPath); + + const tmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us + await mkdirp(tmpPath); + + return { + sourcePath, + targetPath, + tmpPath, + cleanup: async () => { + return rmrf(tmpDirPath); + } + }; + }, + + /** + * Create a temporary directory on the host system + * + * @private + * @param {string} projectName Project name used for naming the temporary directory + * @returns {Promise} Promise resolving with path of the temporary directory + */ + createTmpDir: async function(projectName) { + const sanitizedProjectName = projectName.replace(/[^A-Za-z0-9]/g, ""); + + const tmpRootPath = path.join(os.tmpdir(), "ui5-cli"); + await mkdirp(tmpRootPath); + + // Appending minus sign also because node docs advise to "avoid trailing X characters in prefix" + return mkdtemp(path.join(tmpRootPath, `jsdoc-${sanitizedProjectName}-`)); + }, + + /** + * Write resources from workspace matching the given pattern to the given fs destination + * + * @private + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string} parameters.pattern Pattern to match resources in workspace against + * @param {string} parameters.targetPath Path to write the resources to + * @returns {Promise} Promise resolving with number of resources written to given directory + */ + writeResourcesToDir: async function({workspace, pattern, targetPath}) { + const fsTarget = createAdapter({ + fsBasePath: targetPath, + virBasePath: "/resources/" + }); + + const allResources = await workspace.byGlob(pattern); + + // write all resources to the tmp folder + await Promise.all(allResources.map((resource) => fsTarget.write(resource))); + return allResources.length; + }, + + /** + * Write api.json files of dependencies to given target path in a flat structure + * + * @private + * @param {object} parameters Parameters + * @param {@ui5/fs/AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {string} parameters.targetPath Path to write the resources to + * @returns {Promise} Promise resolving with number of resources written to given directory + */ + writeDependencyApisToDir: async function({dependencies, targetPath}) { + const depApis = await dependencies.byGlob("/test-resources/**/designtime/api.json"); + + // Clone resources before changing their path + const apis = await Promise.all(depApis.map((resource) => resource.clone())); + + for (let i = 0; i < apis.length; i++) { + apis[i].setPath(`/api-${i}.json`); + } + + const fsTarget = createAdapter({ + fsBasePath: targetPath, + virBasePath: "/" + }); + await Promise.all(apis.map((resource) => fsTarget.write(resource))); + return apis.length; + } +}; + +// Export utils for testing only +/* istanbul ignore else */ +if (process.env.NODE_ENV === "test") { + generateJsdoc._utils = utils; +} diff --git a/packages/builder/lib/tasks/minify.js b/packages/builder/lib/tasks/minify.js new file mode 100644 index 00000000000..2969ca688dc --- /dev/null +++ b/packages/builder/lib/tasks/minify.js @@ -0,0 +1,72 @@ +import minifier from "../processors/minifier.js"; +import fsInterface from "@ui5/fs/fsInterface"; + +/** + * @public + * @module @ui5/builder/tasks/minify + */ + +/** + * Task to minify resources. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @param {boolean} [parameters.options.omitSourceMapResources=false] Whether source map resources shall + * be tagged as "OmitFromBuildResult" and no sourceMappingURL shall be added to the minified resource + * @param {boolean} [parameters.options.useInputSourceMaps=true] Whether to make use of any existing source + * maps referenced in the resources to be minified. Use this option to preserve reference to the original + * source files, such as TypeScript files, in the generated source map. + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({ + workspace, taskUtil, options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true + }}) { + const resources = await workspace.byGlob(pattern); + const processedResources = await minifier({ + resources, + fs: fsInterface(workspace), + taskUtil, + options: { + addSourceMappingUrl: !omitSourceMapResources, + readSourceMappingUrl: !!useInputSourceMaps, + useWorkers: !!taskUtil, + } + }); + + return Promise.all(processedResources.map(async ({ + resource, dbgResource, sourceMapResource, dbgSourceMapResource + }) => { + if (taskUtil) { + // Carry over OmitFromBuildResult from input resource to all derived resources + if (taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult)) { + taskUtil.setTag(dbgResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + taskUtil.setTag(sourceMapResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + taskUtil.setTag(resource, taskUtil.STANDARD_TAGS.HasDebugVariant); + taskUtil.setTag(dbgResource, taskUtil.STANDARD_TAGS.IsDebugVariant); + taskUtil.setTag(sourceMapResource, taskUtil.STANDARD_TAGS.HasDebugVariant); + if (omitSourceMapResources) { + taskUtil.setTag(sourceMapResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + if (dbgSourceMapResource) { + taskUtil.setTag(dbgSourceMapResource, taskUtil.STANDARD_TAGS.IsDebugVariant); + if (omitSourceMapResources) { + taskUtil.setTag(dbgSourceMapResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } + } + } + return Promise.all([ + workspace.write(resource), + workspace.write(dbgResource), + workspace.write(sourceMapResource), + dbgSourceMapResource && workspace.write(dbgSourceMapResource) + ]); + })); +} diff --git a/packages/builder/lib/tasks/replaceBuildtime.js b/packages/builder/lib/tasks/replaceBuildtime.js new file mode 100644 index 00000000000..f4093c0b732 --- /dev/null +++ b/packages/builder/lib/tasks/replaceBuildtime.js @@ -0,0 +1,53 @@ +import stringReplacer from "../processors/stringReplacer.js"; + +function pad(v) { + return String(v).padStart(2, "0"); +} +function getTimestamp() { + const date = new Date(); + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + // yyyyMMdd-HHmm + return year + month + day + "-" + hours + minutes; +} + +/** + * @public + * @module @ui5/builder/tasks/replaceBuildtime + */ + +/** + * Task to replace the buildtime ${buildtime}. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default function({workspace, options: {pattern}}) { + const timestamp = getTimestamp(); + + return workspace.byGlob(pattern) + .then((processedResources) => { + return stringReplacer({ + resources: processedResources, + options: { + pattern: "${buildtime}", + replacement: timestamp + } + }); + }) + .then((processedResources) => { + return Promise.all(processedResources.map((resource) => { + return workspace.write(resource); + })); + }); +} diff --git a/packages/builder/lib/tasks/replaceCopyright.js b/packages/builder/lib/tasks/replaceCopyright.js new file mode 100644 index 00000000000..2ccb6a596df --- /dev/null +++ b/packages/builder/lib/tasks/replaceCopyright.js @@ -0,0 +1,55 @@ +import stringReplacer from "../processors/stringReplacer.js"; + +/** + * @public + * @module @ui5/builder/tasks/replaceCopyright + */ + +/** + * Task to to replace the copyright. + * + * The following placeholders are replaced with corresponding values: + *
      + *
    • ${copyright}
    • + *
    • @copyright@
    • + *
    + * + * If the copyright string contains the optional placeholder ${currentYear} + * it will be replaced with the current year. + * If no copyright string is given, no replacement is being done. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.copyright Replacement copyright + * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default function({workspace, options: {copyright, pattern}}) { + if (!copyright) { + return Promise.resolve(); + } + + // Replace optional placeholder ${currentYear} with the current year + copyright = copyright.replace(/(?:\$\{currentYear\})/, new Date().getFullYear()); + + return workspace.byGlob(pattern) + .then((processedResources) => { + return stringReplacer({ + resources: processedResources, + options: { + pattern: /(?:\$\{copyright\}|@copyright@)/g, + replacement: copyright + } + }); + }) + .then((processedResources) => { + return Promise.all(processedResources.map((resource) => { + return workspace.write(resource); + })); + }); +} diff --git a/packages/builder/lib/tasks/replaceVersion.js b/packages/builder/lib/tasks/replaceVersion.js new file mode 100644 index 00000000000..699a6221a95 --- /dev/null +++ b/packages/builder/lib/tasks/replaceVersion.js @@ -0,0 +1,38 @@ +import stringReplacer from "../processors/stringReplacer.js"; + +/** + * @public + * @module @ui5/builder/tasks/replaceVersion + */ + +/** + * Task to replace the version ${version}. + * + * @public + * @function default + * @static + * + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @param {string} parameters.options.version Replacement version + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default function({workspace, options: {pattern, version}}) { + return workspace.byGlob(pattern) + .then((allResources) => { + return stringReplacer({ + resources: allResources, + options: { + pattern: /\$\{(?:project\.)?version\}/g, + replacement: version + } + }); + }) + .then((processedResources) => { + return Promise.all(processedResources.map((resource) => { + return workspace.write(resource); + })); + }); +} diff --git a/packages/builder/lib/tasks/taskRepository.js b/packages/builder/lib/tasks/taskRepository.js new file mode 100644 index 00000000000..9b7b968667a --- /dev/null +++ b/packages/builder/lib/tasks/taskRepository.js @@ -0,0 +1,129 @@ +import {createRequire} from "node:module"; + +/** + * Repository providing access to all UI5 Builder tasks and various metadata required by the build process. + * This module is designed to be imported by @ui5/project or to be passed as a private parameter + * to the build function of a [@ui5/project/graph/ProjectGraph]{@link @ui5/project/graph/ProjectGraph}. + * + * For other use cases, it is recommended to import the required tasks directly. + * + * Therefore, all API of this module is private. + * + * @private + * @module @ui5/builder/tasks/taskRepository + */ + +const taskInfos = { + replaceCopyright: {path: "./replaceCopyright.js"}, + replaceVersion: {path: "./replaceVersion.js"}, + replaceBuildtime: {path: "./replaceBuildtime.js"}, + enhanceManifest: {path: "./enhanceManifest.js"}, + escapeNonAsciiCharacters: {path: "./escapeNonAsciiCharacters.js"}, + executeJsdocSdkTransformation: {path: "./jsdoc/executeJsdocSdkTransformation.js"}, + generateApiIndex: {path: "./jsdoc/generateApiIndex.js"}, + generateJsdoc: {path: "./jsdoc/generateJsdoc.js"}, + minify: {path: "./minify.js"}, + buildThemes: {path: "./buildThemes.js"}, + transformBootstrapHtml: {path: "./transformBootstrapHtml.js"}, + generateLibraryManifest: {path: "./generateLibraryManifest.js"}, + generateVersionInfo: {path: "./generateVersionInfo.js"}, + generateFlexChangesBundle: {path: "./bundlers/generateFlexChangesBundle.js"}, + generateComponentPreload: {path: "./bundlers/generateComponentPreload.js"}, + generateResourcesJson: {path: "./generateResourcesJson.js"}, + generateThemeDesignerResources: {path: "./generateThemeDesignerResources.js"}, + generateStandaloneAppBundle: {path: "./bundlers/generateStandaloneAppBundle.js"}, + generateBundle: {path: "./bundlers/generateBundle.js"}, + generateLibraryPreload: {path: "./bundlers/generateLibraryPreload.js"}, + generateCachebusterInfo: {path: "./generateCachebusterInfo.js"} +}; + + +/** + * TaskInfo object returned by the getTask function + * + * @private + * @typedef {object} @ui5/builder/tasks/taskRepository~TaskInfo + * @property {Function} task Task function + */ + +/** + * Returns the module for a given task name + * + * @private + * @static + * @param {string} taskName Name of the task to retrieve + * @throws {Error} In case the specified task does not exist + * @returns {Promise<@ui5/builder/tasks/taskRepository~TaskInfo>} Object containing the task module + */ +export async function getTask(taskName) { + const taskInfo = taskInfos[taskName]; + + if (!taskInfo) { + if (getRemovedTaskNames().includes(taskName)) { + throw new Error( + `Standard task ${taskName} has been removed in UI5 CLI 3.0. ` + + `Please see the migration guide at https://ui5.github.io/cli/updates/migrate-v3/`); + } + throw new Error(`taskRepository: Unknown Task ${taskName}`); + } + try { + const {default: task} = await import(taskInfo.path); + return { + task + }; + } catch (err) { + throw new Error(`taskRepository: Failed to require task module for ${taskName}: ${err.message}`); + } +} + +/** + * Returns a list of the names of all available tasks + * + * @private + * @static + * @returns {string[]} Array containing the names of all available tasks + */ +export function getAllTaskNames() { + return Object.keys(taskInfos); +} + +/** + * Returns a list of the names of all tasks that have been removed + * in this or previous versions of ui5-builder. + * + * @private + * @static + * @returns {string[]} Array containing the names of all removed tasks + */ +export function getRemovedTaskNames() { + return ["createDebugFiles", "uglify", "generateManifestBundle"]; +} + +// Using CommonsJS require since JSON module imports are still experimental +const require = createRequire(import.meta.url); +function getVersion(pkg) { + return require(`${pkg}/package.json`).version; +} + +/** + * Object containing the versions of the ui5-builder module and relevant dependencies + * + * @private + * @typedef {object} @ui5/builder/tasks/taskRepository~Versions + * @property {string} builderVersion Version of the ui5-builder module + * @property {string} fsVersion Version of the ui5-fs module in use + */ + +/** + * Provides version information on all relevant modules, used by ui5-builder + * + * @private + * @static + * @returns {@ui5/builder/tasks/taskRepository~Versions} Object containing version information + */ +export async function getVersions() { + return { + builderVersion: getVersion("@ui5/builder"), + fsVersion: getVersion("@ui5/fs"), + }; +} diff --git a/packages/builder/lib/tasks/transformBootstrapHtml.js b/packages/builder/lib/tasks/transformBootstrapHtml.js new file mode 100644 index 00000000000..bd10a479bc7 --- /dev/null +++ b/packages/builder/lib/tasks/transformBootstrapHtml.js @@ -0,0 +1,39 @@ +import {getLogger} from "@ui5/logger"; +const log = getLogger("builder:tasks:transformBootstrapHtml"); +import bootstrapHtmlTransformer from "../processors/bootstrapHtmlTransformer.js"; + +/* eslint "jsdoc/check-param-names": ["error", {"disableExtraPropertyReporting":true}] */ +/** + * Task for transforming the application bootstrap HTML file. + * + * @module builder/tasks/transformBootstrapHtml + * @param {object} parameters Parameters + * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @param {string} [parameters.options.projectNamespace] Project namespace + * @returns {Promise} Promise resolving with undefined once data has been written + */ +export default async function({workspace, options}) { + const {projectName} = options; + const namespace = options.projectNamespace; + + let indexPath; + if (namespace) { + indexPath = `/resources/${namespace}/index.html`; + } else { + indexPath = "/index.html"; + } + const resource = await workspace.byPath(indexPath); + if (!resource) { + log.warn(`Skipping bootstrap transformation due to missing index.html in project "${projectName}".`); + return; + } + const processedResources = await bootstrapHtmlTransformer({ + resources: [resource], + options: { + src: "resources/sap-ui-custom.js" + } + }); + await Promise.all(processedResources.map((resource) => workspace.write(resource))); +} diff --git a/packages/builder/lib/tasks/utils/dotTheming.js b/packages/builder/lib/tasks/utils/dotTheming.js new file mode 100644 index 00000000000..91de8fac51c --- /dev/null +++ b/packages/builder/lib/tasks/utils/dotTheming.js @@ -0,0 +1,33 @@ +export async function updateLibraryDotTheming({resource, namespace, version, hasThemes}) { + const dotTheming = JSON.parse(await resource.getString()); + + if (!dotTheming.sEntity) { + throw new Error(`Missing 'sEntity' property in ${resource.getPath()}`); + } + + if (dotTheming.sEntity !== "Library") { + throw new Error( + `Incorrect 'sEntity' value '${dotTheming.sEntity}' in ${resource.getPath()}: ` + + `Expected 'Library'` + ); + } + + if (!dotTheming.sId) { + throw new Error(`Missing 'sId' property in ${resource.getPath()}`); + } + + if (dotTheming.sId !== namespace) { + throw new Error(`Incorrect 'sId' value '${dotTheming.sId}' in ${resource.getPath()}: Expected '${namespace}'`); + } + + dotTheming.sVersion = version; + + if (!hasThemes) { + // Set ignore flag when there are no themes at all + // This is important in case a library used to contain themes that have been removed + // in a later version of the library. + dotTheming.bIgnore = true; + } + + resource.setString(JSON.stringify(dotTheming, null, 2)); +} diff --git a/packages/builder/lib/utils/fs.js b/packages/builder/lib/utils/fs.js new file mode 100644 index 00000000000..a8852509b82 --- /dev/null +++ b/packages/builder/lib/utils/fs.js @@ -0,0 +1,12 @@ +import fs from "graceful-fs"; +import {promisify} from "node:util"; +const mkdir = promisify(fs.mkdir); +const rm = promisify(fs.rm); + +export async function mkdirp(dirPath) { + return mkdir(dirPath, {recursive: true}); +} + +export async function rmrf(dirPath) { + return rm(dirPath, {recursive: true, force: true}); +} diff --git a/packages/builder/package-lock.json b/packages/builder/package-lock.json new file mode 100644 index 00000000000..73a08a5ecc2 --- /dev/null +++ b/packages/builder/package-lock.json @@ -0,0 +1,9116 @@ +{ + "name": "@ui5/builder", + "version": "4.0.11", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ui5/builder", + "version": "4.0.11", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5", + "@ui5/fs": "^4.0.2", + "@ui5/logger": "^4.0.2", + "cheerio": "1.0.0", + "escape-unicode": "^0.2.0", + "escope": "^4.0.0", + "espree": "^10.4.0", + "graceful-fs": "^4.2.11", + "jsdoc": "^4.0.4", + "less-openui5": "^0.11.6", + "pretty-data": "^0.40.0", + "semver": "^7.7.2", + "terser": "^5.44.0", + "workerpool": "^9.3.4", + "xml2js": "^0.6.2" + }, + "devDependencies": { + "@eslint/js": "^9.14.0", + "@istanbuljs/esm-loader-hook": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@ui5/project": "^4.0.6", + "ava": "^6.4.1", + "chokidar-cli": "^3.0.0", + "cross-env": "^7.0.3", + "depcheck": "^1.4.7", + "docdash": "^2.0.2", + "eslint": "^9.36.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-ava": "^15.1.0", + "eslint-plugin-jsdoc": "^52.0.4", + "esmock": "^2.7.3", + "globals": "^16.4.0", + "line-column": "^1.0.2", + "nyc": "^17.1.0", + "open-cli": "^8.0.0", + "rimraf": "^6.0.1", + "sinon": "^21.0.0", + "tap-xunit": "^2.4.1" + }, + "engines": { + "node": "^20.11.0 || >=22.0.0", + "npm": ">= 8" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.52.0.tgz", + "integrity": "sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.34.1", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@istanbuljs/esm-loader-hook": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/esm-loader-hook/-/esm-loader-hook-0.3.0.tgz", + "integrity": "sha512-lEnYroBUYfNQuJDYrPvre8TSwPZnyIQv9qUT3gACvhr3igZr+BbrdyIcz4+2RnEXZzi12GqkUW600+QQPpIbVg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.8.7", + "@babel/plugin-syntax-decorators": "^7.25.9", + "@babel/preset-typescript": "^7.26.0", + "@istanbuljs/load-nyc-config": "^1.1.0", + "@istanbuljs/schema": "^0.1.3", + "babel-plugin-istanbul": "^6.0.0", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=16.12.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz", + "integrity": "sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==", + "dev": true, + "dependencies": { + "consola": "^3.2.3", + "detect-libc": "^2.0.0", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^2.6.7", + "nopt": "^8.0.0", + "semver": "^7.5.3", + "tar": "^7.4.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/config": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-9.0.0.tgz", + "integrity": "sha512-P5Vi16Y+c8E0prGIzX112ug7XxqfaPFUVW/oXAV+2VsxplKZEnJozqZ0xnK8V8w/SEsBf+TXhUihrEIAU4CA5Q==", + "dev": true, + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "dev": true, + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-4.0.2.tgz", + "integrity": "sha512-mnuMuibEbkaBTYj9HQ3dMe6L0ylYW+s/gfz7tBDMFY/la0w9Kf44P9aLn4/+/t3aTR3YUHKoT6XQL9rlicIe3Q==", + "dev": true, + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-3.0.0.tgz", + "integrity": "sha512-61cDL8LUc9y80fXn+lir+iVt8IS0xHqEKwPu/5jCjxQTVoSCmkXvw4vbMrzAMtmghz3/AkiBjhHkDKUH+kf7kA==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", + "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "dev": true, + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sigstore/bundle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", + "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", + "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", + "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ui5/fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@ui5/fs/-/fs-4.0.2.tgz", + "integrity": "sha512-0R7eb9xEMswvkN2wIiyYJtQY83evQJ7LQhTnRf5Ms0o2R29twGLP4XewqH+IoGWyT3T4SuDNTWmUU2UaTRY4zg==", + "dependencies": { + "@ui5/logger": "^4.0.1", + "clone": "^2.1.2", + "escape-string-regexp": "^5.0.0", + "globby": "^14.1.0", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "minimatch": "^10.0.3", + "pretty-hrtime": "^1.0.3", + "random-int": "^3.0.0" + }, + "engines": { + "node": "^20.11.0 || >=22.0.0", + "npm": ">= 8" + } + }, + "node_modules/@ui5/logger": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@ui5/logger/-/logger-4.0.2.tgz", + "integrity": "sha512-uscDCQyHFeenh4r2RbYuffTMn6IQdcNC1tXrQ4BF+apAFjmDGP11IHdAwVCKwxgyPrIC17HT2gub3ZugGM8kpQ==", + "dependencies": { + "chalk": "^5.6.0", + "cli-progress": "^3.12.0", + "figures": "^6.1.0" + }, + "engines": { + "node": "^20.11.0 || >=22.0.0", + "npm": ">= 8" + } + }, + "node_modules/@ui5/project": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@ui5/project/-/project-4.0.6.tgz", + "integrity": "sha512-ryopHahdKxU9GXXfEf3RNQxodHOOdqUmzrQ1uymoa3Om83BMBcURs3FQERjOP4soDbM5RiI7wEdaXX9VRvgH4w==", + "dev": true, + "dependencies": { + "@npmcli/config": "^9.0.0", + "@ui5/fs": "^4.0.2", + "@ui5/logger": "^4.0.2", + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "chalk": "^5.6.2", + "escape-string-regexp": "^5.0.0", + "globby": "^14.1.0", + "graceful-fs": "^4.2.11", + "js-yaml": "^4.1.0", + "lockfile": "^1.0.4", + "make-fetch-happen": "^14.0.3", + "node-stream-zip": "^1.15.0", + "pacote": "^19.0.1", + "pretty-hrtime": "^1.0.3", + "read-package-up": "^11.0.0", + "read-pkg": "^9.0.1", + "resolve": "^1.22.10", + "semver": "^7.7.2", + "xml2js": "^0.6.2", + "yesno": "^0.4.0" + }, + "engines": { + "node": "^20.11.0 || >=22.0.0", + "npm": ">= 8" + }, + "peerDependencies": { + "@ui5/builder": "^4.0.11" + }, + "peerDependenciesMeta": { + "@ui5/builder": { + "optional": true + } + } + }, + "node_modules/@ui5/project/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@ui5/project/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@vercel/nft": { + "version": "0.29.4", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.4.tgz", + "integrity": "sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==", + "dev": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^2.0.0", + "@rollup/pluginutils": "^5.1.3", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^10.4.5", + "graceful-fs": "^4.2.9", + "node-gyp-build": "^4.2.2", + "picomatch": "^4.0.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", + "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", + "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", + "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.21", + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.18", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", + "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", + "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", + "dev": true + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aggregate-error/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrgv": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", + "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "dev": true + }, + "node_modules/ava": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/ava/-/ava-6.4.1.tgz", + "integrity": "sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==", + "dev": true, + "dependencies": { + "@vercel/nft": "^0.29.4", + "acorn": "^8.15.0", + "acorn-walk": "^8.3.4", + "ansi-styles": "^6.2.1", + "arrgv": "^1.0.2", + "arrify": "^3.0.0", + "callsites": "^4.2.0", + "cbor": "^10.0.9", + "chalk": "^5.4.1", + "chunkd": "^2.0.1", + "ci-info": "^4.3.0", + "ci-parallel-vars": "^1.0.1", + "cli-truncate": "^4.0.0", + "code-excerpt": "^4.0.0", + "common-path-prefix": "^3.0.0", + "concordance": "^5.0.4", + "currently-unhandled": "^0.4.1", + "debug": "^4.4.1", + "emittery": "^1.2.0", + "figures": "^6.1.0", + "globby": "^14.1.0", + "ignore-by-default": "^2.1.0", + "indent-string": "^5.0.0", + "is-plain-object": "^5.0.0", + "is-promise": "^4.0.0", + "matcher": "^5.0.0", + "memoize": "^10.1.0", + "ms": "^2.1.3", + "p-map": "^7.0.3", + "package-config": "^5.0.0", + "picomatch": "^4.0.2", + "plur": "^5.1.0", + "pretty-ms": "^9.2.0", + "resolve-cwd": "^3.0.0", + "stack-utils": "^2.0.6", + "strip-ansi": "^7.1.0", + "supertap": "^3.0.1", + "temp-dir": "^3.0.0", + "write-file-atomic": "^6.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "ava": "entrypoints/cli.mjs" + }, + "engines": { + "node": "^18.18 || ^20.8 || ^22 || ^23 || >=24" + }, + "peerDependencies": { + "@ava/typescript": "*" + }, + "peerDependenciesMeta": { + "@ava/typescript": { + "optional": true + } + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", + "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", + "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cbor": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.11.tgz", + "integrity": "sha512-vIwORDd/WyB8Nc23o2zNN5RrtFGlR6Fca61TtjkUXueI3Jf2DOZDl1zsshvBntZ3wZHBM9ztjnkXSmzQDaq3WA==", + "dev": true, + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz", + "integrity": "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "yargs": "^13.3.0" + }, + "bin": { + "chokidar": "index.js" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/chokidar-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/chokidar-cli/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/chokidar-cli/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/chokidar-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/chunkd": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", + "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", + "dev": true + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/ci-parallel-vars": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", + "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concordance": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", + "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", + "dev": true, + "dependencies": { + "date-time": "^3.1.0", + "esutils": "^2.0.3", + "fast-diff": "^1.2.0", + "js-string-escape": "^1.0.1", + "lodash": "^4.17.15", + "md5-hex": "^3.0.1", + "semver": "^7.3.2", + "well-known-symbols": "^2.0.0" + }, + "engines": { + "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/date-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", + "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", + "dev": true, + "dependencies": { + "time-zone": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depcheck": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-1.4.7.tgz", + "integrity": "sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.0", + "@babel/traverse": "^7.23.2", + "@vue/compiler-sfc": "^3.3.4", + "callsite": "^1.0.0", + "camelcase": "^6.3.0", + "cosmiconfig": "^7.1.0", + "debug": "^4.3.4", + "deps-regex": "^0.2.0", + "findup-sync": "^5.0.0", + "ignore": "^5.2.4", + "is-core-module": "^2.12.0", + "js-yaml": "^3.14.1", + "json5": "^2.2.3", + "lodash": "^4.17.21", + "minimatch": "^7.4.6", + "multimatch": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "readdirp": "^3.6.0", + "require-package-name": "^2.0.1", + "resolve": "^1.22.3", + "resolve-from": "^5.0.0", + "semver": "^7.5.4", + "yargs": "^16.2.0" + }, + "bin": { + "depcheck": "bin/depcheck.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/depcheck/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/depcheck/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/depcheck/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/depcheck/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depcheck/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/depcheck/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/depcheck/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/depcheck/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/depcheck/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/depcheck/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/depcheck/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/depcheck/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/deps-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.2.0.tgz", + "integrity": "sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==", + "dev": true + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/docdash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/docdash/-/docdash-2.0.2.tgz", + "integrity": "sha512-3SDDheh9ddrwjzf6dPFe1a16M6ftstqTNjik2+1fx46l24H9dD2osT2q9y+nBEC1wWz4GIqA48JmicOLQ0R8xA==", + "dev": true, + "dependencies": { + "@jsdoc/salty": "^0.2.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", + "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", + "dev": true + }, + "node_modules/emittery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.2.0.tgz", + "integrity": "sha512-KxdRyyFcS85pH3dnU8Y5yFUm2YJdaHwcBZWrfG8o89ZY9a13/f9itbN+YG3ELbBo9Pg5zvIozstmuV8bX13q6g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/enhance-visitors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/enhance-visitors/-/enhance-visitors-1.0.0.tgz", + "integrity": "sha512-+29eJLiUixTEDRaZ35Vu8jP3gPLNcQQkQkOQjLp2X+6cZGGPDD/uasbFzvLsJKnGZnvmyZ0srxudwOtskHeIDA==", + "dev": true, + "dependencies": { + "lodash": "^4.13.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-unicode": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/escape-unicode/-/escape-unicode-0.2.0.tgz", + "integrity": "sha512-7jMQuKb8nm0h/9HYLfu4NCLFwoUsd5XO6OZ1z86PbKcMf8zDK1m7nFR0iA2CCShq4TSValaLIveE8T1UBxgALQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/escope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-4.0.0.tgz", + "integrity": "sha512-E36qlD/r6RJHVpPKArgMoMlNJzoRJFH8z/cAZlI9lbc45zB3+S7i9k6e/MNb+7bZQzNEa6r8WKN3BovpeIBwgA==", + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-ava": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-ava/-/eslint-plugin-ava-15.1.0.tgz", + "integrity": "sha512-+6Zxk1uYW3mf7lxCLWIQsFYgn3hfuCMbsKc0MtqfloOz1F6fiV5/PaWEaLgkL1egrSQmnyR7vOFP1wSPJbVUbw==", + "dev": true, + "dependencies": { + "enhance-visitors": "^1.0.0", + "eslint-utils": "^3.0.0", + "espree": "^9.0.0", + "espurify": "^2.1.1", + "import-modules": "^2.1.0", + "micro-spelling-correcter": "^1.1.1", + "pkg-dir": "^5.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "peerDependencies": { + "eslint": ">=9" + } + }, + "node_modules/eslint-plugin-ava/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-ava/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "52.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-52.0.4.tgz", + "integrity": "sha512-be5OzGlLExvcK13Il3noU7/v7WmAQGenTmCaBKf1pwVtPOb6X+PGFVnJad0QhMj4KKf45XjE4hbsBxv25q1fTg==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.52.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.4.1", + "escape-string-regexp": "^4.0.0", + "espree": "^10.4.0", + "esquery": "^1.6.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": ">=20.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esmock": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.7.3.tgz", + "integrity": "sha512-/M/YZOjgyLaVoY6K83pwCsGE1AJQnj4S4GyXLYgi/Y79KL8EeW6WU7Rmjc89UO7jv6ec8+j34rKeWOfiLeEu0A==", + "dev": true, + "engines": { + "node": ">=14.16.0" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/espurify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-2.1.1.tgz", + "integrity": "sha512-zttWvnkhcDyGOhSH4vO2qCBILpdCMv/MX8lp4cqgRkQoDRGK2oZxi2GfWhlP2dIXmk7BaKeOTuzbHhyC68o8XQ==", + "dev": true + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-to-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", + "integrity": "sha512-inRWzRY7nG+aXZxBzEqYKB3HPgwflZRopAjDCHv0whhRx+MTUr1ei0ICZUypdyE0HRm4L2d5VEcIqLD6yl+BFA==", + "dev": true + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "18.7.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.7.0.tgz", + "integrity": "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==", + "dev": true, + "dependencies": { + "readable-web-to-node-stream": "^3.0.2", + "strtok3": "^7.0.0", + "token-types": "^5.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", + "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", + "dev": true, + "engines": { + "node": ">=10 <11 || >=12 <13 || >=14" + } + }, + "node_modules/ignore-walk": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", + "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-modules": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-modules/-/import-modules-2.1.0.tgz", + "integrity": "sha512-8HEWcnkbGpovH9yInoisxaSoIg9Brbul+Ju3Kqe2UsYDUBJD/iQjSgEj0zPcTDPKfPp2fs5xlv1i+JSye/m1/A==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/less-openui5": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/less-openui5/-/less-openui5-0.11.6.tgz", + "integrity": "sha512-sQmU+G2pJjFfzRI+XtXkk+T9G0s6UmWWUfOW0utPR46C9lfhNr4DH1lNJuImj64reXYi+vOwyNxPRkj0F3mofA==", + "dependencies": { + "@adobe/css-tools": "^4.0.2", + "clone": "^2.1.2", + "mime": "^1.6.0" + }, + "engines": { + "node": ">= 10", + "npm": ">= 5" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==", + "dev": true, + "dependencies": { + "isarray": "^1.0.0", + "isobject": "^2.0.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/load-json-file": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", + "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "dev": true, + "dependencies": { + "signal-exit": "^3.0.2" + } + }, + "node_modules/lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/matcher": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", + "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/md5-hex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", + "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", + "dev": true, + "dependencies": { + "blueimp-md5": "^2.10.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/memoize": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.1.0.tgz", + "integrity": "sha512-MMbFhJzh4Jlg/poq1si90XRlTZRDHVqdlz2mPyGJ6kqMpyHUyVpDd5gpFAvVehW64+RA1eKE9Yt8aSLY7w2Kgg==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micro-spelling-correcter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/micro-spelling-correcter/-/micro-spelling-correcter-1.1.1.tgz", + "integrity": "sha512-lkJ3Rj/mtjlRcHk6YyCbvZhyWTOzdBvTHsxMmZSk5jxN1YyVSQ+JETAom55mdzfcyDrY/49Z7UCW760BK30crg==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/multimatch/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.4.2.tgz", + "integrity": "sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-install-checks": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", + "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-package-arg": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-packlist": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-9.0.0.tgz", + "integrity": "sha512-8qSayfmHJQTx3nJWYbbUmflpyarbLMBc6LCAjYsiGtXxDB68HaZpb8re6zeaLGxZzDuMdhsg70jryJe+RrItVQ==", + "dev": true, + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "dev": true, + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nyc": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz", + "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^3.3.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^6.0.2", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nyc/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/nyc/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-8.0.0.tgz", + "integrity": "sha512-3muD3BbfLyzl+aMVSEfn2FfOqGdPYR0O4KNnxXsLEPE2q9OSjBfJAaB6XKbrUzLgymoSMejvb5jpXJfru/Ko2A==", + "dev": true, + "dependencies": { + "file-type": "^18.7.0", + "get-stdin": "^9.0.0", + "meow": "^12.1.1", + "open": "^10.0.0", + "tempy": "^3.1.0" + }, + "bin": { + "open-cli": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-config": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", + "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "load-json-file": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/pacote": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-19.0.1.tgz", + "integrity": "sha512-zIpxWAsr/BvhrkSruspG8aqCQUUrWtpwx0GjiRZQhEM/pZXrigA32ElN3vTcCPUDOFmHr6SFxwYrvVUs5NTEUg==", + "dev": true, + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/pacote/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pacote/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pacote/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pacote/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/pacote/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pacote/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pacote/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/peek-readable": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", + "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/plur": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "dev": true, + "dependencies": { + "irregular-plurals": "^3.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-data": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/pretty-data/-/pretty-data-0.40.0.tgz", + "integrity": "sha512-YFLnEdDEDnkt/GEhet5CYZHCvALw6+Elyb/tp8kQG03ZSIuzeaDWpZYndCXwgqu4NAjh1PI534dhDS1mHarRnQ==", + "engines": { + "node": "*" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/random-int": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/random-int/-/random-int-3.1.0.tgz", + "integrity": "sha512-h8CRz8cpvzj0hC/iH/1Gapgcl2TQ6xtnCpyOI5WvWfXf/yrDx2DOU+tD9rX23j36IF11xg1KqB9W11Z18JPMdw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "dev": true, + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/require-package-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", + "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==", + "dev": true + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/sinon": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.5", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/spawn-wrap/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/spawn-wrap/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/spawn-wrap/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", + "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.1.3" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supertap": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", + "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", + "dev": true, + "dependencies": { + "indent-string": "^5.0.0", + "js-yaml": "^3.14.1", + "serialize-error": "^7.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tap-parser": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-1.2.2.tgz", + "integrity": "sha512-uXKcosa0qoSjeh73dhmX+OpJvpigDxUciOhBcbGUKtmwzEFJjUT1Ql5dpg4M9I1UjXT9b+6n1W05FB8QmKossA==", + "dev": true, + "dependencies": { + "events-to-array": "^1.0.1", + "inherits": "~2.0.1", + "js-yaml": "^3.2.7" + }, + "bin": { + "tap-parser": "bin/cmd.js" + }, + "optionalDependencies": { + "readable-stream": "^2" + } + }, + "node_modules/tap-parser/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/tap-parser/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + }, + "node_modules/tap-parser/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tap-xunit": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tap-xunit/-/tap-xunit-2.4.1.tgz", + "integrity": "sha512-qcZStDtjjYjMKAo7QNiCtOW256g3tuSyCSe5kNJniG1Q2oeOExJq4vm8CwboHZURpkXAHvtqMl4TVL7mcbMVVA==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "minimist": "~1.2.0", + "tap-parser": "~1.2.2", + "through2": "~2.0.0", + "xmlbuilder": "~4.2.0", + "xtend": "~4.0.0" + }, + "bin": { + "tap-xunit": "bin/tap-xunit", + "txunit": "bin/tap-xunit" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-types": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", + "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "dev": true, + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", + "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "dev": true, + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/well-known-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", + "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", + "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha512-oEePiEefhQhAeUnwRnIBLBWmk/fsWWbQ53EEWsRuzECbQ3m5o/Esmq6H47CYYwSLW+Ynt0rS9hd0pd2ogMAWjg==", + "dev": true, + "dependencies": { + "lodash": "^4.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yesno": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", + "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/builder/package.json b/packages/builder/package.json new file mode 100644 index 00000000000..27b8b8a045b --- /dev/null +++ b/packages/builder/package.json @@ -0,0 +1,163 @@ +{ + "name": "@ui5/builder", + "version": "4.0.11", + "description": "UI5 CLI - Builder", + "author": { + "name": "SAP SE", + "email": "openui5@sap.com", + "url": "https://www.sap.com" + }, + "license": "Apache-2.0", + "keywords": [ + "openui5", + "sapui5", + "ui5", + "build", + "development", + "tool" + ], + "type": "module", + "exports": { + "./processors/*": "./lib/processors/*.js", + "./processors/jsdoc/lib/*": null, + "./tasks/*": "./lib/tasks/*.js", + "./tasks/taskRepository": null, + "./tasks/utils/*": null, + "./tasks/bundlers/utils/*": null, + "./package.json": "./package.json", + "./internal/taskRepository": "./lib/tasks/taskRepository.js", + "./internal/jsdoc/template/publish": "./lib/processors/jsdoc/lib/ui5/template/publish.js" + }, + "engines": { + "node": "^20.11.0 || >=22.0.0", + "npm": ">= 8" + }, + "scripts": { + "test": "npm run lint && npm run jsdoc-generate && npm run coverage && npm run depcheck", + "test-azure": "npm run coverage-xunit", + "lint": "eslint ./", + "unit": "rimraf test/tmp && ava", + "unit-verbose": "rimraf test/tmp && cross-env UI5_LOG_LVL=verbose ava --verbose --serial", + "unit-watch": "rimraf test/tmp && ava --watch", + "unit-xunit": "rimraf test/tmp && ava --node-arguments=\"--experimental-loader=@istanbuljs/esm-loader-hook\" --tap --timeout=1m | tap-xunit --dontUseCommentsAsTestNames=true > test-results.xml", + "unit-inspect": "cross-env UI5_LOG_LVL=verbose ava debug --break", + "coverage": "rimraf test/tmp && nyc ava --node-arguments=\"--experimental-loader=@istanbuljs/esm-loader-hook\"", + "coverage-xunit": "nyc --reporter=text --reporter=text-summary --reporter=cobertura npm run unit-xunit", + "jsdoc": "npm run jsdoc-generate && open-cli jsdocs/index.html", + "jsdoc-generate": "jsdoc -c ./jsdoc.json -t $(node -p 'path.dirname(require.resolve(\"docdash\"))') ./lib/ || (echo 'Error during JSDoc generation! Check log.' && exit 1)", + "jsdoc-watch": "npm run jsdoc && chokidar \"./lib/**/*.js\" -c \"npm run jsdoc-generate\"", + "preversion": "npm test", + "version": "git-chglog --sort semver --next-tag v$npm_package_version -o CHANGELOG.md v4.0.0.. && git add CHANGELOG.md", + "prepublishOnly": "git push --follow-tags", + "release-note": "git-chglog --sort semver -c .chglog/release-config.yml v$npm_package_version", + "depcheck": "depcheck --ignores @ui5/builder,docdash,@istanbuljs/esm-loader-hook,catharsis,rimraf --parsers='**/*.js:es6,**/*.cjs:es6'" + }, + "files": [ + "CHANGELOG.md", + "CONTRIBUTING.md", + "jsdoc.json", + "lib/**", + "LICENSES/**", + ".reuse/**" + ], + "ava": { + "files": [ + "test/lib/**/*.js" + ], + "watchMode": { + "ignoreChanges": [ + "test/tmp/**" + ] + }, + "nodeArguments": [ + "--loader=esmock", + "--no-warnings" + ], + "workerThreads": false + }, + "nyc": { + "reporter": [ + "lcov", + "text", + "text-summary" + ], + "exclude": [ + "lib/processors/jsdoc/lib/**", + "docs/**", + "jsdocs/**", + "coverage/**", + "test/**", + ".eslintrc.cjs", + "jsdoc-plugin.cjs" + ], + "check-coverage": true, + "statements": 85, + "branches": 70, + "functions": 90, + "lines": 85, + "watermarks": { + "statements": [ + 70, + 90 + ], + "branches": [ + 70, + 90 + ], + "functions": [ + 70, + 90 + ], + "lines": [ + 70, + 90 + ] + }, + "cache": true, + "all": true + }, + "repository": { + "type": "git", + "url": "git@github.com:SAP/ui5-builder.git" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5", + "@ui5/fs": "^4.0.2", + "@ui5/logger": "^4.0.2", + "cheerio": "1.0.0", + "escape-unicode": "^0.2.0", + "escope": "^4.0.0", + "espree": "^10.4.0", + "graceful-fs": "^4.2.11", + "jsdoc": "^4.0.4", + "less-openui5": "^0.11.6", + "pretty-data": "^0.40.0", + "semver": "^7.7.2", + "terser": "^5.44.0", + "workerpool": "^9.3.4", + "xml2js": "^0.6.2" + }, + "devDependencies": { + "@eslint/js": "^9.14.0", + "@istanbuljs/esm-loader-hook": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@ui5/project": "^4.0.6", + "ava": "^6.4.1", + "chokidar-cli": "^3.0.0", + "cross-env": "^7.0.3", + "depcheck": "^1.4.7", + "docdash": "^2.0.2", + "eslint": "^9.36.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-ava": "^15.1.0", + "eslint-plugin-jsdoc": "^52.0.4", + "esmock": "^2.7.3", + "globals": "^16.4.0", + "line-column": "^1.0.2", + "nyc": "^17.1.0", + "open-cli": "^8.0.0", + "rimraf": "^6.0.1", + "sinon": "^21.0.0", + "tap-xunit": "^2.4.1" + } +} diff --git a/packages/builder/test/expected/build/application.a/dest-clean/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest-clean/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-clean/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-clean/index.html b/packages/builder/test/expected/build/application.a/dest-clean/index.html new file mode 100644 index 00000000000..1523b1dc3a9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-clean/index.html @@ -0,0 +1,11 @@ + + + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-clean/manifest.json b/packages/builder/test/expected/build/application.a/dest-clean/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-clean/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-clean/test.js b/packages/builder/test/expected/build/application.a/dest-clean/test.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-clean/test.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest-deps-excl/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/index.html b/packages/builder/test/expected/build/application.a/dest-deps-excl/index.html new file mode 100644 index 00000000000..1523b1dc3a9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/index.html @@ -0,0 +1,11 @@ + + + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/manifest.json b/packages/builder/test/expected/build/application.a/dest-deps-excl/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/.library b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/.library new file mode 100644 index 00000000000..cddfadd9a88 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library A + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css new file mode 100644 index 00000000000..5398b3f0817 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-RTL.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-parameters.json b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-parameters.json new file mode 100644 index 00000000000..da3b7a52fc3 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library-parameters.json @@ -0,0 +1 @@ +{"libraryAColor1":"#fafad2"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css new file mode 100644 index 00000000000..ba056b3c0ea --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.source.less b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/b/.library b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/b/.library new file mode 100644 index 00000000000..8cfb124a93b --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library B + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/c/.library b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/c/.library new file mode 100644 index 00000000000..3c011ecd637 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/resources/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + 1.0.0 + + Library C + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps-excl/test-dbg.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/test-dbg.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/a/Test.html b/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/b/Test.html b/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/c/Test.html b/packages/builder/test/expected/build/application.a/dest-deps-excl/test-resources/library/c/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js b/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js new file mode 100644 index 00000000000..24b3706c658 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js @@ -0,0 +1,2 @@ +sap.ui.define(["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js.map b/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js.map new file mode 100644 index 00000000000..a62b5b5927e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps-excl/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest-deps/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/index.html b/packages/builder/test/expected/build/application.a/dest-deps/index.html new file mode 100644 index 00000000000..1523b1dc3a9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/index.html @@ -0,0 +1,11 @@ + + + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/manifest.json b/packages/builder/test/expected/build/application.a/dest-deps/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/.library b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/.library new file mode 100644 index 00000000000..cddfadd9a88 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library A + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css new file mode 100644 index 00000000000..5398b3f0817 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-RTL.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-parameters.json b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-parameters.json new file mode 100644 index 00000000000..da3b7a52fc3 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library-parameters.json @@ -0,0 +1 @@ +{"libraryAColor1":"#fafad2"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css new file mode 100644 index 00000000000..ba056b3c0ea --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.source.less b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/b/.library b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/b/.library new file mode 100644 index 00000000000..8cfb124a93b --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library B + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/c/.library b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/c/.library new file mode 100644 index 00000000000..3c011ecd637 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + 1.0.0 + + Library C + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/.library b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/.library new file mode 100644 index 00000000000..d6a612a9241 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + 1.0.0 + + Library D + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty-dbg.js new file mode 100644 index 00000000000..c82b694dc1c --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty-dbg.js @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */ diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js new file mode 100644 index 00000000000..8af07c6008c --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ + +//# sourceMappingURL=empty.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js.map b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js.map new file mode 100644 index 00000000000..767ef9fbdc8 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/empty.js.map @@ -0,0 +1 @@ +{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy-dbg.js new file mode 100644 index 00000000000..dc97b0e1fdc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar = "Old World"; +console.log('Hello ' + topLevelVar); diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js new file mode 100644 index 00000000000..8238eb084f2 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar="Old World";console.log("Hello "+topLevelVar); +//# sourceMappingURL=legacy.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js.map b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js.map new file mode 100644 index 00000000000..d8a842426f0 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/legacy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"legacy.js","names":["topLevelVar","console","log"],"sources":["legacy-dbg.js"],"mappings":";;;AAGA,IAAIA,YAAc,YAClBC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library-dbg.js new file mode 100644 index 00000000000..c024ed2d07e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library-dbg.js @@ -0,0 +1,23 @@ +/* + * Some fancy copyright + */ + +sap.ui.define([ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "1.0.0", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js new file mode 100644 index 00000000000..f9910ae650d --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js @@ -0,0 +1,5 @@ +/* + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Lib","sap/ui/core/library"],function(e){"use strict";return e.init({name:"library.d",version:"1.0.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:[],elements:[],noLibraryCSS:true})}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js.map b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js.map new file mode 100644 index 00000000000..911269e84c3 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Library","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS"],"sources":["library-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CACb,kBACA,uBACE,SAAUC,GACZ,aAEA,OAAOA,EAAQC,KAAK,CACnBC,KAAM,YACNC,QAAS,QACTC,aAAc,CACb,eAEDC,MAAO,GACPC,WAAY,GACZC,SAAU,GACVC,SAAU,GACVC,aAAc,MAEhB","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some-dbg.js new file mode 100644 index 00000000000..3699e19874e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some-dbg.js @@ -0,0 +1,8 @@ +/*! + * Some fancy copyright + */ + +sap.ui.define(["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js new file mode 100644 index 00000000000..8d85629bba9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["./library"],l=>{var o="World";console.log("Hello "+o)}); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js.map b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js.map new file mode 100644 index 00000000000..9daafa1872a --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/library/d/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["sap","ui","define","_library","someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CAAC,aAAeC,IAC7B,IAAIC,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/sap/ui/core/.library b/packages/builder/test/expected/build/application.a/dest-deps/resources/sap/ui/core/.library new file mode 100644 index 00000000000..134688b25fa --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/sap/ui/core/.library @@ -0,0 +1,11 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + 1.0.0 + + Library sap.ui.core + + diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader-dbg.js new file mode 100644 index 00000000000..0e76321fdef --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader-dbg.js @@ -0,0 +1 @@ +// this is just a marker file to enable the EVO bundle format diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js new file mode 100644 index 00000000000..9b62890d846 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js @@ -0,0 +1,2 @@ + +//# sourceMappingURL=ui5loader.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js.map b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js.map new file mode 100644 index 00000000000..67c649ce886 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/resources/ui5loader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ui5loader.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test-dbg.js b/packages/builder/test/expected/build/application.a/dest-deps/test-dbg.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/test-dbg.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/a/Test.html b/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/b/Test.html b/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/c/Test.html b/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/c/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/d/Test.html b/packages/builder/test/expected/build/application.a/dest-deps/test-resources/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test.js b/packages/builder/test/expected/build/application.a/dest-deps/test.js new file mode 100644 index 00000000000..24b3706c658 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/test.js @@ -0,0 +1,2 @@ +sap.ui.define(["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-deps/test.js.map b/packages/builder/test/expected/build/application.a/dest-deps/test.js.map new file mode 100644 index 00000000000..a62b5b5927e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-deps/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest-depself/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/index.html b/packages/builder/test/expected/build/application.a/dest-depself/index.html new file mode 100644 index 00000000000..3c9fe4a815e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/index.html @@ -0,0 +1,9 @@ + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/manifest.json b/packages/builder/test/expected/build/application.a/dest-depself/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/.library b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/.library new file mode 100644 index 00000000000..cddfadd9a88 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library A + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css new file mode 100644 index 00000000000..5398b3f0817 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-RTL.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 4px 3px 2px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-parameters.json b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-parameters.json new file mode 100644 index 00000000000..da3b7a52fc3 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library-parameters.json @@ -0,0 +1 @@ +{"libraryAColor1":"#fafad2"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css new file mode 100644 index 00000000000..ba056b3c0ea --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.css @@ -0,0 +1,3 @@ +.library-a-foo{color:#fafad2;padding:1px 2px 3px 4px} +/* Inline theming parameters */ +#sap-ui-theme-library\.a{background-image:url('data:text/plain;utf-8,%7B%22libraryAColor1%22%3A%22%23fafad2%22%7D')} diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.source.less b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/b/.library b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/b/.library new file mode 100644 index 00000000000..8cfb124a93b --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + Some fancy copyright ${currentYear} + 1.0.0 + + Library B + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/c/.library b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/c/.library new file mode 100644 index 00000000000..3c011ecd637 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + 1.0.0 + + Library C + + + + library.d + + + + diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/.library b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/.library new file mode 100644 index 00000000000..d6a612a9241 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + 1.0.0 + + Library D + + diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty-dbg.js new file mode 100644 index 00000000000..c82b694dc1c --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty-dbg.js @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */ diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js new file mode 100644 index 00000000000..8af07c6008c --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ + +//# sourceMappingURL=empty.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js.map new file mode 100644 index 00000000000..767ef9fbdc8 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/empty.js.map @@ -0,0 +1 @@ +{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy-dbg.js new file mode 100644 index 00000000000..dc97b0e1fdc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar = "Old World"; +console.log('Hello ' + topLevelVar); diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js new file mode 100644 index 00000000000..8238eb084f2 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar="Old World";console.log("Hello "+topLevelVar); +//# sourceMappingURL=legacy.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js.map new file mode 100644 index 00000000000..d8a842426f0 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/legacy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"legacy.js","names":["topLevelVar","console","log"],"sources":["legacy-dbg.js"],"mappings":";;;AAGA,IAAIA,YAAc,YAClBC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library-dbg.js new file mode 100644 index 00000000000..c024ed2d07e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library-dbg.js @@ -0,0 +1,23 @@ +/* + * Some fancy copyright + */ + +sap.ui.define([ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "1.0.0", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js new file mode 100644 index 00000000000..f9910ae650d --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js @@ -0,0 +1,5 @@ +/* + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Lib","sap/ui/core/library"],function(e){"use strict";return e.init({name:"library.d",version:"1.0.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:[],elements:[],noLibraryCSS:true})}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js.map new file mode 100644 index 00000000000..911269e84c3 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Library","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS"],"sources":["library-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CACb,kBACA,uBACE,SAAUC,GACZ,aAEA,OAAOA,EAAQC,KAAK,CACnBC,KAAM,YACNC,QAAS,QACTC,aAAc,CACb,eAEDC,MAAO,GACPC,WAAY,GACZC,SAAU,GACVC,SAAU,GACVC,aAAc,MAEhB","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some-dbg.js new file mode 100644 index 00000000000..3699e19874e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some-dbg.js @@ -0,0 +1,8 @@ +/*! + * Some fancy copyright + */ + +sap.ui.define(["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js new file mode 100644 index 00000000000..8d85629bba9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["./library"],l=>{var o="World";console.log("Hello "+o)}); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js.map new file mode 100644 index 00000000000..9daafa1872a --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/library/d/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["sap","ui","define","_library","someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CAAC,aAAeC,IAC7B,IAAIC,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js new file mode 100644 index 00000000000..54dbb5e7d2b --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js @@ -0,0 +1,2 @@ +//@ui5-bundle sap-ui-custom-dbg.js +//# sourceMappingURL=sap-ui-custom-dbg.js.map diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js.map new file mode 100644 index 00000000000..7c185dcc4f6 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom-dbg.js","sections":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js new file mode 100644 index 00000000000..caa96229864 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js @@ -0,0 +1,14 @@ +//@ui5-bundle sap-ui-custom.js +sap.ui.predefine("id1/test", ["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +/* + * Some fancy copyright + */ +sap.ui.predefine("library/d/library", ["sap/ui/core/Lib","sap/ui/core/library"],function(e){"use strict";return e.init({name:"library.d",version:"1.0.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:[],elements:[],noLibraryCSS:true})}); +/*! + * Some fancy copyright + */ +sap.ui.predefine("library/d/some", ["./library"],l=>{var o="World";console.log("Hello "+o)}); +sap.ui.require.preload({ + "id1/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=sap-ui-custom.js.map diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js.map new file mode 100644 index 00000000000..7bfce703d5a --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap-ui-custom.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,sBAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[],"sourceRoot":"id1"}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","Library","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAIAA,IAAIC,GAAGC,+BAAO,CACb,kBACA,uBACE,SAAUC,GACZ,aAEA,OAAOA,EAAQC,KAAK,CACnBC,KAAM,YACNC,QAAS,QACTC,aAAc,CACb,eAEDC,MAAO,GACPC,WAAY,GACZC,SAAU,GACVC,SAAU,GACVC,aAAc,MAEhB","ignoreList":[],"sourceRoot":"library/d"}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":["sap","ui","define","_library","someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;AAIAA,IAAIC,GAAGC,4BAAO,CAAC,aAAeC,IAC7B,IAAIC,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF","ignoreList":[],"sourceRoot":"library/d"}},{"offset":{"line":10,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/sap/ui/core/.library b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap/ui/core/.library new file mode 100644 index 00000000000..134688b25fa --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/sap/ui/core/.library @@ -0,0 +1,11 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + 1.0.0 + + Library sap.ui.core + + diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader-dbg.js new file mode 100644 index 00000000000..0e76321fdef --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader-dbg.js @@ -0,0 +1 @@ +// this is just a marker file to enable the EVO bundle format diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js new file mode 100644 index 00000000000..9b62890d846 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js @@ -0,0 +1,2 @@ + +//# sourceMappingURL=ui5loader.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js.map b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js.map new file mode 100644 index 00000000000..67c649ce886 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/resources/ui5loader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ui5loader.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test-dbg.js b/packages/builder/test/expected/build/application.a/dest-depself/test-dbg.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/test-dbg.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/a/Test.html b/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/b/Test.html b/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/c/Test.html b/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/c/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/d/Test.html b/packages/builder/test/expected/build/application.a/dest-depself/test-resources/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test.js b/packages/builder/test/expected/build/application.a/dest-depself/test.js new file mode 100644 index 00000000000..24b3706c658 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/test.js @@ -0,0 +1,2 @@ +sap.ui.define(["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-depself/test.js.map b/packages/builder/test/expected/build/application.a/dest-depself/test.js.map new file mode 100644 index 00000000000..a62b5b5927e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-depself/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest-self/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/index.html b/packages/builder/test/expected/build/application.a/dest-self/index.html new file mode 100644 index 00000000000..3c9fe4a815e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/index.html @@ -0,0 +1,9 @@ + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/manifest.json b/packages/builder/test/expected/build/application.a/dest-self/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js new file mode 100644 index 00000000000..54dbb5e7d2b --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js @@ -0,0 +1,2 @@ +//@ui5-bundle sap-ui-custom-dbg.js +//# sourceMappingURL=sap-ui-custom-dbg.js.map diff --git a/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js.map b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js.map new file mode 100644 index 00000000000..7c185dcc4f6 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom-dbg.js","sections":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js new file mode 100644 index 00000000000..bfabf268571 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js @@ -0,0 +1,15 @@ +//@ui5-bundle sap-ui-custom.js +sap.ui.predefine("id1/test", ["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +/* + * Some fancy copyright + */ +sap.ui.predefine("library/d/library", ["sap/ui/core/Lib","sap/ui/core/library"],function(e){"use strict";return e.init({name:"library.d",version:"1.0.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:[],elements:[],noLibraryCSS:true})}); +/*! + * Some fancy copyright + */ +sap.ui.predefine("library/d/some", ["./library"],l=>{var o="World";console.log("Hello "+o)}); +sap.ui.require.preload({ + "id1/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "library/d/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"library.d","type":"library","embeds":[],"applicationVersion":{"version":"1.0.0"},"title":"Library D","description":"Library D","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":[]},"sap.ui5":{"dependencies":{"minUI5Version":"1.0","libs":{}},"library":{"i18n":false,"css":false,"content":{"controls":[],"elements":[],"types":[],"interfaces":[]}}}}' +}); +//# sourceMappingURL=sap-ui-custom.js.map diff --git a/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js.map b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js.map new file mode 100644 index 00000000000..7bfce703d5a --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/resources/sap-ui-custom.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,sBAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[],"sourceRoot":"id1"}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","Library","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAIAA,IAAIC,GAAGC,+BAAO,CACb,kBACA,uBACE,SAAUC,GACZ,aAEA,OAAOA,EAAQC,KAAK,CACnBC,KAAM,YACNC,QAAS,QACTC,aAAc,CACb,eAEDC,MAAO,GACPC,WAAY,GACZC,SAAU,GACVC,SAAU,GACVC,aAAc,MAEhB","ignoreList":[],"sourceRoot":"library/d"}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":["sap","ui","define","_library","someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;AAIAA,IAAIC,GAAGC,4BAAO,CAAC,aAAeC,IAC7B,IAAIC,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF","ignoreList":[],"sourceRoot":"library/d"}},{"offset":{"line":10,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/test-dbg.js b/packages/builder/test/expected/build/application.a/dest-self/test-dbg.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/test-dbg.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest-self/test.js b/packages/builder/test/expected/build/application.a/dest-self/test.js new file mode 100644 index 00000000000..24b3706c658 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/test.js @@ -0,0 +1,2 @@ +sap.ui.define(["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest-self/test.js.map b/packages/builder/test/expected/build/application.a/dest-self/test.js.map new file mode 100644 index 00000000000..a62b5b5927e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest-self/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest/fileWithoutExtension b/packages/builder/test/expected/build/application.a/dest/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest/index.html b/packages/builder/test/expected/build/application.a/dest/index.html new file mode 100644 index 00000000000..1523b1dc3a9 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/index.html @@ -0,0 +1,11 @@ + + + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest/manifest.json b/packages/builder/test/expected/build/application.a/dest/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest/test-dbg.js b/packages/builder/test/expected/build/application.a/dest/test-dbg.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/test-dbg.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.a/dest/test.js b/packages/builder/test/expected/build/application.a/dest/test.js new file mode 100644 index 00000000000..24b3706c658 --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/test.js @@ -0,0 +1,2 @@ +sap.ui.define(["library/d/some"],function(n){function o(n){var o=n;console.log(o)}o()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.a/dest/test.js.map b/packages/builder/test/expected/build/application.a/dest/test.js.map new file mode 100644 index 00000000000..a62b5b5927e --- /dev/null +++ b/packages/builder/test/expected/build/application.a/dest/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","someObject","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CACV,kBACD,SAASC,GACX,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n.properties new file mode 100644 index 00000000000..d93c8a39c0e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n.properties @@ -0,0 +1 @@ +title=embedded-i18n \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n_de.properties new file mode 100644 index 00000000000..e513333c842 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/embedded/i18n/i18n_de.properties @@ -0,0 +1 @@ +title=embedded-i18n_de \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/embedded/i18n_fr.properties b/packages/builder/test/expected/build/application.b/dest/embedded/i18n_fr.properties new file mode 100644 index 00000000000..85e162740f9 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/embedded/i18n_fr.properties @@ -0,0 +1 @@ +title=embedded-i18n_fr-wrong \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/embedded/manifest.json b/packages/builder/test/expected/build/application.b/dest/embedded/manifest.json new file mode 100644 index 00000000000..5ef0c362425 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/embedded/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1.embedded", + "type": "component", + "applicationVersion": { + "version": "1.2.2" + }, + "embeddedBy": "../", + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/i18n.properties b/packages/builder/test/expected/build/application.b/dest/i18n.properties new file mode 100644 index 00000000000..e8dc9e0da08 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/dest/i18n/i18n.properties new file mode 100644 index 00000000000..17772dbf51c --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/i18n/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/dest/i18n/i18n_de.properties new file mode 100644 index 00000000000..d2c23da4be7 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/i18n/i18n_de.properties @@ -0,0 +1,2 @@ +title=app-i18n_de +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/i18n/l10n.properties b/packages/builder/test/expected/build/application.b/dest/i18n/l10n.properties new file mode 100644 index 00000000000..e8dc9e0da08 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/i18n/l10n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle.zip b/packages/builder/test/expected/build/application.b/dest/manifest-bundle.zip new file mode 100644 index 00000000000..d068b47f1f2 Binary files /dev/null and b/packages/builder/test/expected/build/application.b/dest/manifest-bundle.zip differ diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n.properties new file mode 100644 index 00000000000..d93c8a39c0e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n.properties @@ -0,0 +1 @@ +title=embedded-i18n \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n_de.properties new file mode 100644 index 00000000000..e513333c842 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/i18n/i18n_de.properties @@ -0,0 +1 @@ +title=embedded-i18n_de \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/manifest.json b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/manifest.json new file mode 100644 index 00000000000..5ef0c362425 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/embedded/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1.embedded", + "type": "component", + "applicationVersion": { + "version": "1.2.2" + }, + "embeddedBy": "../", + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n.properties new file mode 100644 index 00000000000..17772dbf51c --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n_de.properties new file mode 100644 index 00000000000..d2c23da4be7 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/i18n/i18n_de.properties @@ -0,0 +1,2 @@ +title=app-i18n_de +fame=Stra\u00dfe \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/dest/manifest-bundle/manifest.json b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/manifest.json new file mode 100644 index 00000000000..d33902df74e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest-bundle/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.b/dest/manifest.json b/packages/builder/test/expected/build/application.b/dest/manifest.json new file mode 100644 index 00000000000..d33902df74e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/dest/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n.properties new file mode 100644 index 00000000000..d93c8a39c0e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n.properties @@ -0,0 +1 @@ +title=embedded-i18n \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n_de.properties new file mode 100644 index 00000000000..e513333c842 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n/i18n_de.properties @@ -0,0 +1 @@ +title=embedded-i18n_de \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/embedded/i18n_fr.properties b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n_fr.properties new file mode 100644 index 00000000000..85e162740f9 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/embedded/i18n_fr.properties @@ -0,0 +1 @@ +title=embedded-i18n_fr-wrong \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/embedded/manifest.json b/packages/builder/test/expected/build/application.b/standalone/embedded/manifest.json new file mode 100644 index 00000000000..5ef0c362425 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/embedded/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1.embedded", + "type": "component", + "applicationVersion": { + "version": "1.2.2" + }, + "embeddedBy": "../", + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/i18n.properties b/packages/builder/test/expected/build/application.b/standalone/i18n.properties new file mode 100644 index 00000000000..d8d43be34d3 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/i18n/i18n.properties b/packages/builder/test/expected/build/application.b/standalone/i18n/i18n.properties new file mode 100644 index 00000000000..49f4756f982 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/i18n/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/i18n/i18n_de.properties b/packages/builder/test/expected/build/application.b/standalone/i18n/i18n_de.properties new file mode 100644 index 00000000000..a81193360b3 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/i18n/i18n_de.properties @@ -0,0 +1,2 @@ +title=app-i18n_de +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/i18n/l10n.properties b/packages/builder/test/expected/build/application.b/standalone/i18n/l10n.properties new file mode 100644 index 00000000000..d8d43be34d3 --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/i18n/l10n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/manifest.json b/packages/builder/test/expected/build/application.b/standalone/manifest.json new file mode 100644 index 00000000000..d33902df74e --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js new file mode 100644 index 00000000000..aeed9a3d2ac --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js @@ -0,0 +1,10 @@ +//@ui5-bundle sap-ui-custom-dbg.js +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function () { + var thisIsTheUi5LoaderAutoconfig = true; + console.log(thisIsTheUi5LoaderAutoconfig); +})() +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +//# sourceMappingURL=sap-ui-custom-dbg.js.map diff --git a/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js.map b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js.map new file mode 100644 index 00000000000..6ced624ac2f --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom-dbg.js","sections":[{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom-dbg.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js new file mode 100644 index 00000000000..855779cc21c --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js @@ -0,0 +1,21 @@ +//@ui5-bundle sap-ui-custom.js +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function(){var o=true;console.log(o)})(); +sap.ui.require.preload({ + "id1/embedded/i18n/i18n.properties":'title=embedded-i18n', + "id1/embedded/i18n/i18n_de.properties":'title=embedded-i18n_de', + "id1/embedded/i18n_fr.properties":'title=embedded-i18n_fr-wrong', + "id1/embedded/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1.embedded","type":"component","applicationVersion":{"version":"1.2.2"},"embeddedBy":"../","title":"{{title}}"}}', + "id1/i18n.properties":'title=app-i18n-wrong\nfame=Stra\\u00dfe', + "id1/i18n/i18n.properties":'title=app-i18n\nfame=Stra\\u00dfe', + "id1/i18n/i18n_de.properties":'title=app-i18n_de\nfame=Stra\\u00dfe', + "id1/i18n/l10n.properties":'title=app-i18n-wrong\nfame=Stra\\u00dfe', + "id1/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "sap/ui/core/Core.js":function(){ +(function(){var o=true;console.log(o)})(); +} +}); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +//# sourceMappingURL=sap-ui-custom.js.map diff --git a/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js.map b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js.map new file mode 100644 index 00000000000..dcf672eb33c --- /dev/null +++ b/packages/builder/test/expected/build/application.b/standalone/resources/sap-ui-custom.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom.js","sections":[{"offset":{"line":2,"column":0},"map":{"version":3,"file":"ui5loader-autoconfig.js","names":["thisIsTheUi5LoaderAutoconfig","console","log"],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAA+B,KACnCC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":14,"column":0},"map":{"version":3,"file":"Core.js","names":["core","console","log"],"sources":["Core-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAAO,KACXC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":"sap/ui/core"}},{"offset":{"line":17,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":18,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-2"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster/Component-dbg.js new file mode 100644 index 00000000000..fd86d4bd8f7 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js b/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js new file mode 100644 index 00000000000..18f436dcbed --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js @@ -0,0 +1,10 @@ +//@ui5-bundle application/g/Component-preload.js +sap.ui.predefine("application/g/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/g/subcomponentA/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/g/subcomponentB/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g","type":"application","applicationVersion":{"version":"1.0.0"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"Some fancy copyright"}', + "application/g/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "application/g/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js.map b/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js.map new file mode 100644 index 00000000000..2fc5c0b38f2 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":"subcomponentA"}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":"subcomponentB"}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/Component.js b/packages/builder/test/expected/build/application.g/cachebuster/Component.js new file mode 100644 index 00000000000..6ad3cfb4111 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster/manifest.json new file mode 100644 index 00000000000..6207053349b --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "Some fancy copyright" +} diff --git a/packages/builder/test/expected/build/application.g/cachebuster/sap-ui-cachebuster-info.json b/packages/builder/test/expected/build/application.g/cachebuster/sap-ui-cachebuster-info.json new file mode 100644 index 00000000000..84b5b3467e3 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/sap-ui-cachebuster-info.json @@ -0,0 +1,16 @@ +{ + "Component-dbg.js": 1710766618901, + "subcomponentA/Component.js": 1710766618902, + "subcomponentB/manifest.json": 1710766618902, + "subcomponentB/Component.js": 1710766618902, + "subcomponentA/manifest.json": 1710766618902, + "Component.js": 1710766618901, + "manifest.json": 1710766618901, + "Component.js.map": 1716293079735, + "subcomponentB/Component-dbg.js": 1710766618902, + "subcomponentB/Component.js.map": 1716293079737, + "subcomponentA/Component-dbg.js": 1710766618902, + "subcomponentA/Component.js.map": 1716293079735, + "Component-preload.js": 1716293079931, + "Component-preload.js.map": 1716293079931 +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component-dbg.js new file mode 100644 index 00000000000..5f1b47a8a61 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js new file mode 100644 index 00000000000..b53909394d4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component-dbg.js new file mode 100644 index 00000000000..5fb07ed25c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js new file mode 100644 index 00000000000..c5cc2084d9d --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/manifest.json new file mode 100644 index 00000000000..84cd1802f51 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-dbg.js new file mode 100644 index 00000000000..fd86d4bd8f7 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js new file mode 100644 index 00000000000..18f436dcbed --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js @@ -0,0 +1,10 @@ +//@ui5-bundle application/g/Component-preload.js +sap.ui.predefine("application/g/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/g/subcomponentA/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/g/subcomponentB/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g","type":"application","applicationVersion":{"version":"1.0.0"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"Some fancy copyright"}', + "application/g/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "application/g/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js.map b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js.map new file mode 100644 index 00000000000..2fc5c0b38f2 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":"subcomponentA"}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":"subcomponentB"}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js new file mode 100644 index 00000000000..6ad3cfb4111 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster_hash/manifest.json new file mode 100644 index 00000000000..6207053349b --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "Some fancy copyright" +} diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/sap-ui-cachebuster-info.json b/packages/builder/test/expected/build/application.g/cachebuster_hash/sap-ui-cachebuster-info.json new file mode 100644 index 00000000000..8af67c58cde --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/sap-ui-cachebuster-info.json @@ -0,0 +1,16 @@ +{ + "Component-preload.js": "77489dcf574cf1a2253a15d2cf6cfa68f4f25464", + "subcomponentA/Component.js": "37e2d3c99ddf728313ceaddd087c6d547fcbdf8f", + "subcomponentA/manifest.json": "7dfcdf568dcfb47c74d309e2f00193688b112fb7", + "subcomponentB/Component.js": "d2a467cc8d05c693b22dbef82c2bff4610611d81", + "manifest.json": "a867583cff8c3a5135bfbdd97dcb7a9cad4948bd", + "Component.js": "174e92417f0fa7889c2bdb8a243c4d748d66a4da", + "subcomponentA/Component-dbg.js": "fef3a5814776e5575302f814e96ae31a13df12b7", + "subcomponentA/Component.js.map": "13121aa3fc198d95365b5f68befaf16ff0672e18", + "subcomponentB/Component-dbg.js": "c03da2c5ff162c1d9d5e5c4a3edc2cebcda6b0a1", + "subcomponentB/Component.js.map": "13121aa3fc198d95365b5f68befaf16ff0672e18", + "Component-dbg.js": "874c35543ae850262d1552b6c2d00cecaae205a7", + "Component.js.map": "44cc85428ecef8cd2bb90c991e91144081360803", + "subcomponentB/manifest.json": "3a08d0555e3e49fe683e01205090eed3182b4a9f", + "Component-preload.js.map": "ff7ae83ab12cc0744db9f77093bf1c3055cb67d9" +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component-dbg.js new file mode 100644 index 00000000000..5f1b47a8a61 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js new file mode 100644 index 00000000000..b53909394d4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component-dbg.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component-dbg.js new file mode 100644 index 00000000000..5fb07ed25c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js new file mode 100644 index 00000000000..c5cc2084d9d --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js.map b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/manifest.json new file mode 100644 index 00000000000..84cd1802f51 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/cachebuster_hash/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest/Component-dbg.js new file mode 100644 index 00000000000..fd86d4bd8f7 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/Component-preload.js b/packages/builder/test/expected/build/application.g/dest/Component-preload.js new file mode 100644 index 00000000000..762c8c33364 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/Component-preload.js +sap.ui.predefine("application/g/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g","type":"application","applicationVersion":{"version":"1.0.0"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"Some fancy copyright"}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest/Component-preload.js.map new file mode 100644 index 00000000000..c1c1fa62ad1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/Component.js b/packages/builder/test/expected/build/application.g/dest/Component.js new file mode 100644 index 00000000000..6ad3cfb4111 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/Component.js.map b/packages/builder/test/expected/build/application.g/dest/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/manifest.json b/packages/builder/test/expected/build/application.g/dest/manifest.json new file mode 100644 index 00000000000..6207053349b --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "Some fancy copyright" +} diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-dbg.js new file mode 100644 index 00000000000..5f1b47a8a61 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js new file mode 100644 index 00000000000..8897b8d6ac1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/subcomponentA/Component-preload.js +sap.ui.predefine("application/g/subcomponentA/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js.map new file mode 100644 index 00000000000..40574da5738 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js new file mode 100644 index 00000000000..b53909394d4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js.map b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.g/dest/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-dbg.js new file mode 100644 index 00000000000..5fb07ed25c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js new file mode 100644 index 00000000000..6503ee8a407 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/subcomponentB/Component-preload.js +sap.ui.predefine("application/g/subcomponentB/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js.map new file mode 100644 index 00000000000..40574da5738 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js new file mode 100644 index 00000000000..c5cc2084d9d --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js.map b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.g/dest/subcomponentB/manifest.json new file mode 100644 index 00000000000..84cd1802f51 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest2/Component-dbg.js new file mode 100644 index 00000000000..fd86d4bd8f7 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/Component-preload.js b/packages/builder/test/expected/build/application.g/dest2/Component-preload.js new file mode 100644 index 00000000000..762c8c33364 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/Component-preload.js +sap.ui.predefine("application/g/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g","type":"application","applicationVersion":{"version":"1.0.0"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"Some fancy copyright"}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest2/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest2/Component-preload.js.map new file mode 100644 index 00000000000..c1c1fa62ad1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/Component.js b/packages/builder/test/expected/build/application.g/dest2/Component.js new file mode 100644 index 00000000000..6ad3cfb4111 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/Component.js.map b/packages/builder/test/expected/build/application.g/dest2/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/manifest.json b/packages/builder/test/expected/build/application.g/dest2/manifest.json new file mode 100644 index 00000000000..6207053349b --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "Some fancy copyright" +} diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-dbg.js new file mode 100644 index 00000000000..5f1b47a8a61 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js new file mode 100644 index 00000000000..8897b8d6ac1 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/subcomponentA/Component-preload.js +sap.ui.predefine("application/g/subcomponentA/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js.map new file mode 100644 index 00000000000..40574da5738 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js new file mode 100644 index 00000000000..b53909394d4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentA.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js.map b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-dbg.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-dbg.js new file mode 100644 index 00000000000..5fb07ed25c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js new file mode 100644 index 00000000000..6503ee8a407 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js @@ -0,0 +1,6 @@ +//@ui5-bundle application/g/subcomponentB/Component-preload.js +sap.ui.predefine("application/g/subcomponentB/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/g/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.g.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js.map b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js.map new file mode 100644 index 00000000000..40574da5738 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,mDAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js new file mode 100644 index 00000000000..c5cc2084d9d --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.subcomponentB.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js.map b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js.map new file mode 100644 index 00000000000..221398d54ba --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,wCAAyC,CAClEC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/dest2/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/manifest.json new file mode 100644 index 00000000000..84cd1802f51 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/dest2/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.g/excludes/manifest.json b/packages/builder/test/expected/build/application.g/excludes/manifest.json new file mode 100644 index 00000000000..56cc0ec3be9 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/excludes/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "${copyright}" +} diff --git a/packages/builder/test/expected/build/application.g/excludes/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.g/excludes/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/expected/build/application.g/excludes/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/Component-dbg.js b/packages/builder/test/expected/build/application.h/dest/Component-dbg.js new file mode 100644 index 00000000000..cb9bd406864 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.h.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/Component.js b/packages/builder/test/expected/build/application.h/dest/Component.js new file mode 100644 index 00000000000..ccde1164649 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.h.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/Component.js.map b/packages/builder/test/expected/build/application.h/dest/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/manifest.json b/packages/builder/test/expected/build/application.h/dest/manifest.json new file mode 100644 index 00000000000..32b7e4a8458 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.h", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js new file mode 100644 index 00000000000..5036a1964ab --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js @@ -0,0 +1,4 @@ +//@ui5-bundle application/h/sectionsA/customBundle.js +sap.ui.predefine("application/h/sectionsA/section1", ["sap/m/Button"],function(n){console.log("Section 1 included")}); +sap.ui.predefine("application/h/sectionsA/section3", ["sap/m/Button"],function(n){console.log("Section 3 included")}); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js.map new file mode 100644 index 00000000000..6e464712d47 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","Button","console","log"],"sources":["section1-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,8CAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","Button","console","log"],"sources":["section3-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,8CAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section1-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1-dbg.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js new file mode 100644 index 00000000000..7409303672a --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 1 included")}); +//# sourceMappingURL=section1.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js.map new file mode 100644 index 00000000000..f786da345aa --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section1.js","names":["sap","ui","define","Button","console","log"],"sources":["section1-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section2-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2-dbg.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js new file mode 100644 index 00000000000..653acdb8339 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 2 included")}); +//# sourceMappingURL=section2.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js.map new file mode 100644 index 00000000000..e8c86449703 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section2.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section2.js","names":["sap","ui","define","Button","console","log"],"sources":["section2-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section3-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3-dbg.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js new file mode 100644 index 00000000000..70c1434ed55 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 3 included")}); +//# sourceMappingURL=section3.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js.map new file mode 100644 index 00000000000..a577550e9ac --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsA/section3.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section3.js","names":["sap","ui","define","Button","console","log"],"sources":["section3-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js new file mode 100644 index 00000000000..545f0451ba0 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js @@ -0,0 +1,11 @@ +//@ui5-bundle application/h/sectionsB/customBundle.js +sap.ui.predefine("application/h/sectionsB/section1", ["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); +sap.ui.predefine("application/h/sectionsB/section2", ["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); +sap.ui.predefine("application/h/sectionsB/section3", ["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js.map new file mode 100644 index 00000000000..6dff1304fdd --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["section1-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["section2-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"names":[],"sources":["section3-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section1-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1-dbg.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js new file mode 100644 index 00000000000..7409303672a --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 1 included")}); +//# sourceMappingURL=section1.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js.map new file mode 100644 index 00000000000..f786da345aa --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section1.js","names":["sap","ui","define","Button","console","log"],"sources":["section1-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section2-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2-dbg.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js new file mode 100644 index 00000000000..653acdb8339 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 2 included")}); +//# sourceMappingURL=section2.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js.map new file mode 100644 index 00000000000..e8c86449703 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section2.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section2.js","names":["sap","ui","define","Button","console","log"],"sources":["section2-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section3-dbg.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3-dbg.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js new file mode 100644 index 00000000000..70c1434ed55 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/m/Button"],function(n){console.log("Section 3 included")}); +//# sourceMappingURL=section3.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js.map b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js.map new file mode 100644 index 00000000000..a577550e9ac --- /dev/null +++ b/packages/builder/test/expected/build/application.h/dest/sectionsB/section3.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section3.js","names":["sap","ui","define","Button","console","log"],"sources":["section3-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,gBAAiB,SAASC,GACxCC,QAAQC,IAAI,qBACb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/no-minify/Component.js b/packages/builder/test/expected/build/application.h/no-minify/Component.js new file mode 100644 index 00000000000..cb9bd406864 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.h.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/manifest.json b/packages/builder/test/expected/build/application.h/no-minify/manifest.json new file mode 100644 index 00000000000..32b7e4a8458 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.h", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js new file mode 100644 index 00000000000..0934f10aeeb --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js @@ -0,0 +1,8 @@ +//@ui5-bundle application/h/sectionsA/customBundle.js +sap.ui.predefine("application/h/sectionsA/section1", ["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); +sap.ui.predefine("application/h/sectionsA/section3", ["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js.map b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js.map new file mode 100644 index 00000000000..a71546ff5b3 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["section1.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["section3.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section1.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section1.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section2.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section2.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section3.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section3.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsA/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js new file mode 100644 index 00000000000..545f0451ba0 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js @@ -0,0 +1,11 @@ +//@ui5-bundle application/h/sectionsB/customBundle.js +sap.ui.predefine("application/h/sectionsB/section1", ["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); +sap.ui.predefine("application/h/sectionsB/section2", ["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); +sap.ui.predefine("application/h/sectionsB/section3", ["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js.map b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js.map new file mode 100644 index 00000000000..a772b090bb8 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["section1.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["section2.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"names":[],"sources":["section3.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section1.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section1.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section2.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section2.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section3.js b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section3.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/expected/build/application.h/no-minify/sectionsB/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/expected/build/application.i/dest/Component-dbg.js b/packages/builder/test/expected/build/application.i/dest/Component-dbg.js new file mode 100644 index 00000000000..f402cb1409d --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.i.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.i/dest/Component-preload.js b/packages/builder/test/expected/build/application.i/dest/Component-preload.js new file mode 100644 index 00000000000..c75c4d9080b --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/Component-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle application/i/Component-preload.js +sap.ui.predefine("application/i/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.i.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/i/changes/coding/MyExtension", [],function(){return{}}); +sap.ui.require.preload({ + "application/i/changes/changes-bundle.json":'[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.i.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.i.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.i.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.i.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}]', + "application/i/changes/fragments/MyFragment.fragment.xml":'', + "application/i/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.i","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"},"sap.ui5":{"dependencies":{"minUI5Version":"1.72","libs":{"sap.ui.layout":{},"sap.ui.core":{},"sap.m":{},"sap.ui.fl":{}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.i/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.i/dest/Component-preload.js.map new file mode 100644 index 00000000000..5e0f3bc48f4 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,sDAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[],"sourceRoot":"changes/coding"}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/Component.js b/packages/builder/test/expected/build/application.i/dest/Component.js new file mode 100644 index 00000000000..5d50a2effaf --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.i.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/Component.js.map b/packages/builder/test/expected/build/application.i/dest/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/changes/changes-bundle.json b/packages/builder/test/expected/build/application.i/dest/changes/changes-bundle.json new file mode 100644 index 00000000000..8cba061aec7 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/changes/changes-bundle.json @@ -0,0 +1 @@ +[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.i.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.i.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.i.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.i.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}] \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension-dbg.js b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension-dbg.js new file mode 100644 index 00000000000..dfb3da014ac --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define([],function () { + return {}; +}); diff --git a/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js new file mode 100644 index 00000000000..ce81ba31044 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js @@ -0,0 +1,2 @@ +sap.ui.define([],function(){return{}}); +//# sourceMappingURL=MyExtension.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js.map b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js.map new file mode 100644 index 00000000000..9f10e974fe0 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/changes/coding/MyExtension.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyExtension.js","names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/changes/fragments/MyFragment.fragment.xml b/packages/builder/test/expected/build/application.i/dest/changes/fragments/MyFragment.fragment.xml new file mode 100644 index 00000000000..39ce6859b45 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/changes/fragments/MyFragment.fragment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.i/dest/manifest.json b/packages/builder/test/expected/build/application.i/dest/manifest.json new file mode 100644 index 00000000000..b40142f3040 --- /dev/null +++ b/packages/builder/test/expected/build/application.i/dest/manifest.json @@ -0,0 +1,26 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.i", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": [ + "embedded" + ], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.72", + "libs": { + "sap.ui.layout": {}, + "sap.ui.core": {}, + "sap.m": {}, + "sap.ui.fl": {} + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/Component-dbg.js b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-dbg.js new file mode 100644 index 00000000000..e40d21d9a27 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.j.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js new file mode 100644 index 00000000000..58f6a5a4714 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle application/j/Component-preload.js +sap.ui.predefine("application/j/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/j/changes/coding/MyExtension", [],function(){return{}}); +sap.ui.require.preload({ + "application/j/changes/flexibility-bundle.json":'{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}]}', + "application/j/changes/fragments/MyFragment.fragment.xml":'', + "application/j/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.j","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"},"sap.ui5":{"dependencies":{"minUI5Version":"1.100.2","libs":{"sap.ui.layout":{},"sap.ui.core":{},"sap.m":{},"sap.ui.fl":{"lazy":false}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js.map b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js.map new file mode 100644 index 00000000000..5e0f3bc48f4 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,sDAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[],"sourceRoot":"changes/coding"}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js b/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js new file mode 100644 index 00000000000..f64b0734d4a --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js.map b/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension-dbg.js b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension-dbg.js new file mode 100644 index 00000000000..dfb3da014ac --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define([],function () { + return {}; +}); diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js new file mode 100644 index 00000000000..ce81ba31044 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js @@ -0,0 +1,2 @@ +sap.ui.define([],function(){return{}}); +//# sourceMappingURL=MyExtension.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js.map b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js.map new file mode 100644 index 00000000000..9f10e974fe0 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyExtension.js","names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json new file mode 100644 index 00000000000..d1315a19264 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json @@ -0,0 +1 @@ +{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml new file mode 100644 index 00000000000..39ce6859b45 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/fileWithoutExtension b/packages/builder/test/expected/build/application.j/dest-resources-json/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/manifest.json b/packages/builder/test/expected/build/application.j/dest-resources-json/manifest.json new file mode 100644 index 00000000000..e9fe49460e7 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.j", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": [ + "embedded" + ], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.100.2", + "libs": { + "sap.ui.layout": {}, + "sap.ui.core": {}, + "sap.m": {}, + "sap.ui.fl": { + "lazy": false + } + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/resources.json b/packages/builder/test/expected/build/application.j/dest-resources-json/resources.json new file mode 100644 index 00000000000..291f07a2f89 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/resources.json @@ -0,0 +1,99 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Component-dbg.js", + "module": "application/j/Component.js", + "size": 184, + "isDebug": true, + "required": [ + "sap/m/library.js", + "sap/ui/core/UIComponent.js", + "sap/ui/core/library.js", + "sap/ui/fl/library.js", + "sap/ui/layout/library.js" + ] + }, + { + "name": "Component-preload.js", + "module": "application/j/Component-preload.js", + "size": 3658, + "merged": true, + "required": [ + "sap/m/library.js", + "sap/ui/core/UIComponent.js", + "sap/ui/core/library.js", + "sap/ui/fl/library.js", + "sap/ui/layout/library.js" + ], + "included": [ + "application/j/Component.js", + "application/j/changes/coding/MyExtension.js", + "application/j/changes/flexibility-bundle.json", + "application/j/changes/fragments/MyFragment.fragment.xml", + "application/j/manifest.json" + ] + }, + { + "name": "Component-preload.js.map", + "size": 790, + "isDebug": true + }, + { + "name": "Component.js", + "module": "application/j/Component.js", + "size": 179, + "required": [ + "sap/m/library.js", + "sap/ui/core/UIComponent.js", + "sap/ui/core/library.js", + "sap/ui/fl/library.js", + "sap/ui/layout/library.js" + ] + }, + { + "name": "Component.js.map", + "size": 274, + "isDebug": true + }, + { + "name": "changes/coding/MyExtension-dbg.js", + "module": "application/j/changes/coding/MyExtension.js", + "size": 47, + "isDebug": true + }, + { + "name": "changes/coding/MyExtension.js", + "module": "application/j/changes/coding/MyExtension.js", + "size": 79 + }, + { + "name": "changes/coding/MyExtension.js.map", + "size": 178, + "isDebug": true + }, + { + "name": "changes/flexibility-bundle.json", + "module": "application/j/changes/flexibility-bundle.json", + "size": 2800 + }, + { + "name": "changes/fragments/MyFragment.fragment.xml", + "module": "application/j/changes/fragments/MyFragment.fragment.xml", + "size": 13 + }, + { + "name": "fileWithoutExtension", + "size": 27 + }, + { + "name": "manifest.json", + "module": "application/j/manifest.json", + "size": 424 + }, + { + "name": "resources.json", + "size": 2173 + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json b/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json new file mode 100644 index 00000000000..551fc7d927c --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json @@ -0,0 +1,7 @@ +{ + "name": "application.j", + "version": "1.0.0", + "buildTimestamp": "202008120917", + "scmRevision": "", + "libraries": [] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/Component-dbg.js b/packages/builder/test/expected/build/application.j/dest/Component-dbg.js new file mode 100644 index 00000000000..e40d21d9a27 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/Component-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.j.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.j/dest/Component-preload.js b/packages/builder/test/expected/build/application.j/dest/Component-preload.js new file mode 100644 index 00000000000..58f6a5a4714 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/Component-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle application/j/Component-preload.js +sap.ui.predefine("application/j/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("application/j/changes/coding/MyExtension", [],function(){return{}}); +sap.ui.require.preload({ + "application/j/changes/flexibility-bundle.json":'{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}]}', + "application/j/changes/fragments/MyFragment.fragment.xml":'', + "application/j/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.j","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"},"sap.ui5":{"dependencies":{"minUI5Version":"1.100.2","libs":{"sap.ui.layout":{},"sap.ui.core":{},"sap.m":{},"sap.ui.fl":{"lazy":false}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.j/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.j/dest/Component-preload.js.map new file mode 100644 index 00000000000..5e0f3bc48f4 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,sDAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[],"sourceRoot":"changes/coding"}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/Component.js b/packages/builder/test/expected/build/application.j/dest/Component.js new file mode 100644 index 00000000000..f64b0734d4a --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/Component.js.map b/packages/builder/test/expected/build/application.j/dest/Component.js.map new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension-dbg.js b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension-dbg.js new file mode 100644 index 00000000000..dfb3da014ac --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension-dbg.js @@ -0,0 +1,3 @@ +sap.ui.define([],function () { + return {}; +}); diff --git a/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js new file mode 100644 index 00000000000..ce81ba31044 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js @@ -0,0 +1,2 @@ +sap.ui.define([],function(){return{}}); +//# sourceMappingURL=MyExtension.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js.map b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js.map new file mode 100644 index 00000000000..9f10e974fe0 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/changes/coding/MyExtension.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyExtension.js","names":["sap","ui","define"],"sources":["MyExtension-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,GAAG,WAChB,MAAO,CAAC,CACT","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/changes/flexibility-bundle.json b/packages/builder/test/expected/build/application.j/dest/changes/flexibility-bundle.json new file mode 100644 index 00000000000..d1315a19264 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/changes/flexibility-bundle.json @@ -0,0 +1 @@ +{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"CUSTOMER","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"SAP"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/changes/fragments/MyFragment.fragment.xml b/packages/builder/test/expected/build/application.j/dest/changes/fragments/MyFragment.fragment.xml new file mode 100644 index 00000000000..39ce6859b45 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/changes/fragments/MyFragment.fragment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/fileWithoutExtension b/packages/builder/test/expected/build/application.j/dest/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.j/dest/manifest.json b/packages/builder/test/expected/build/application.j/dest/manifest.json new file mode 100644 index 00000000000..e9fe49460e7 --- /dev/null +++ b/packages/builder/test/expected/build/application.j/dest/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.j", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": [ + "embedded" + ], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.100.2", + "libs": { + "sap.ui.layout": {}, + "sap.ui.core": {}, + "sap.m": {}, + "sap.ui.fl": { + "lazy": false + } + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js new file mode 100644 index 00000000000..2f991f6c484 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js @@ -0,0 +1,34 @@ +//@ui5-bundle application/k/Component-preload.js +sap.ui.predefine("application/k/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.predefine("application/k/subcomponentA/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.predefine("application/k/subcomponentB/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.require.preload({ + "application/k/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k","type":"application","applicationVersion":{"version":"${version}"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"${copyright}"}', + "application/k/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "application/k/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "application/k/subcomponentB/thirdparty/lib.js":function(){ +console.log("subcomponentB/thirdparty/lib.js"); +} +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js.map b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js.map new file mode 100644 index 00000000000..f746f9c940b --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":"subcomponentA"}},{"offset":{"line":17,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":"subcomponentB"}},{"offset":{"line":25,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":30,"column":0},"map":{"version":3,"names":[],"sources":["lib.js"],"mappings":"AAAA;AACA","sourceRoot":"subcomponentB/thirdparty"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component.js new file mode 100644 index 00000000000..b386a26a9c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/manifest.json b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/manifest.json new file mode 100644 index 00000000000..ffed9444e37 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "${copyright}" +} diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/Component.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/Component.js new file mode 100644 index 00000000000..faed14a03eb --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/manifest.json new file mode 100644 index 00000000000..f6d29cb2dbf --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/thirdparty/lib.js new file mode 100644 index 00000000000..cd35259d2b4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentA/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentA/thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/Component.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/Component.js new file mode 100644 index 00000000000..b713ad894c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/manifest.json new file mode 100644 index 00000000000..88f052556d0 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/thirdparty/lib.js new file mode 100644 index 00000000000..6f4d127a4c3 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/subcomponentB/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentB/thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.k/dest-package-subcomponents/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/thirdparty/lib.js new file mode 100644 index 00000000000..b52c6b26a70 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest-package-subcomponents/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.k/dest/Component-preload.js b/packages/builder/test/expected/build/application.k/dest/Component-preload.js new file mode 100644 index 00000000000..d1e7e96e051 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/Component-preload.js @@ -0,0 +1,13 @@ +//@ui5-bundle application/k/Component-preload.js +sap.ui.predefine("application/k/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.require.preload({ + "application/k/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k","type":"application","applicationVersion":{"version":"${version}"},"embeds":["embedded"],"title":"{{title}}"},"customCopyrightString":"${copyright}"}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.k/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.k/dest/Component-preload.js.map new file mode 100644 index 00000000000..1c495111b3f --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.k/dest/Component.js b/packages/builder/test/expected/build/application.k/dest/Component.js new file mode 100644 index 00000000000..b386a26a9c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest/manifest.json b/packages/builder/test/expected/build/application.k/dest/manifest.json new file mode 100644 index 00000000000..ffed9444e37 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "${copyright}" +} diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js new file mode 100644 index 00000000000..ccbf87d9a21 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js @@ -0,0 +1,13 @@ +//@ui5-bundle application/k/subcomponentA/Component-preload.js +sap.ui.predefine("application/k/subcomponentA/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.require.preload({ + "application/k/subcomponentA/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k.subcomponentA","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js.map b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js.map new file mode 100644 index 00000000000..1c495111b3f --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component.js b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component.js new file mode 100644 index 00000000000..faed14a03eb --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentA/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentA/manifest.json b/packages/builder/test/expected/build/application.k/dest/subcomponentA/manifest.json new file mode 100644 index 00000000000..f6d29cb2dbf --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentA/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest/subcomponentA/thirdparty/lib.js new file mode 100644 index 00000000000..cd35259d2b4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentA/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentA/thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js new file mode 100644 index 00000000000..dce1ce7d7cc --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js @@ -0,0 +1,16 @@ +//@ui5-bundle application/k/subcomponentB/Component-preload.js +sap.ui.predefine("application/k/subcomponentB/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); +sap.ui.require.preload({ + "application/k/subcomponentB/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.k.subcomponentB","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}', + "application/k/subcomponentB/thirdparty/lib.js":function(){ +console.log("subcomponentB/thirdparty/lib.js"); +} +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js.map b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js.map new file mode 100644 index 00000000000..31e360e894d --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":12,"column":0},"map":{"version":3,"names":[],"sources":["lib.js"],"mappings":"AAAA;AACA","sourceRoot":"thirdparty"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component.js b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component.js new file mode 100644 index 00000000000..b713ad894c4 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentB/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentB/manifest.json b/packages/builder/test/expected/build/application.k/dest/subcomponentB/manifest.json new file mode 100644 index 00000000000..88f052556d0 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.k/dest/subcomponentB/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest/subcomponentB/thirdparty/lib.js new file mode 100644 index 00000000000..6f4d127a4c3 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/subcomponentB/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentB/thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.k/dest/thirdparty/lib.js b/packages/builder/test/expected/build/application.k/dest/thirdparty/lib.js new file mode 100644 index 00000000000..b52c6b26a70 --- /dev/null +++ b/packages/builder/test/expected/build/application.k/dest/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("thirdparty/lib.js"); diff --git a/packages/builder/test/expected/build/application.l/dest/index.html b/packages/builder/test/expected/build/application.l/dest/index.html new file mode 100644 index 00000000000..4c3be4c7cbd --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/index.html @@ -0,0 +1,11 @@ + + + + Application L + + + + + + diff --git a/packages/builder/test/expected/build/application.l/dest/manifest.json b/packages/builder/test/expected/build/application.l/dest/manifest.json new file mode 100644 index 00000000000..2e0d63d4ec5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.l", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/index-dbg.js b/packages/builder/test/expected/build/application.l/dest/subdir/index-dbg.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/index-dbg.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/index.js b/packages/builder/test/expected/build/application.l/dest/subdir/index.js new file mode 100644 index 00000000000..9661d72076f --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/index.js @@ -0,0 +1,2 @@ +function hello(l){console.log("hello "+l)}hello("world"); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/index.js.map b/packages/builder/test/expected/build/application.l/dest/subdir/index.js.map new file mode 100644 index 00000000000..bd6f85e8c8a --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","names":["hello","name","console","log"],"sources":["index-dbg.js"],"mappings":"AACA,SAASA,MAAMC,GACdC,QAAQC,IAAI,SAAWF,EACxB,CACAD,MAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File0.js b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1-dbg.js b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1-dbg.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1-dbg.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js new file mode 100644 index 00000000000..7b3311e4ee4 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js @@ -0,0 +1,2 @@ +function hello(l){console.log("hello "+l)}hello("world"); +//# sourceMappingURL=File1.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js.map b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js.map new file mode 100644 index 00000000000..1b9082c01f3 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/subdir/thirdparty/File1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"File1.js","names":["hello","name","console","log"],"sources":["File1-dbg.js"],"mappings":"AACA,SAASA,MAAMC,GACdC,QAAQC,IAAI,SAAWF,EACxB,CACAD,MAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/test-dbg.js b/packages/builder/test/expected/build/application.l/dest/test-dbg.js new file mode 100644 index 00000000000..6d767ba0569 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/test-dbg.js @@ -0,0 +1,7 @@ +sap.ui.define([], function() { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/expected/build/application.l/dest/test.js b/packages/builder/test/expected/build/application.l/dest/test.js new file mode 100644 index 00000000000..5a6672e9f02 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/test.js @@ -0,0 +1,2 @@ +sap.ui.define([],function(){function n(n){var o=n;console.log(o)}n()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/test.js.map b/packages/builder/test/expected/build/application.l/dest/test.js.map new file mode 100644 index 00000000000..87c1e39bbb9 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,GAAI,WACjB,SAASC,EAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF,GACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.l/dest/thirdparty/File0.js b/packages/builder/test/expected/build/application.l/dest/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/application.l/dest/thirdparty/File1.js b/packages/builder/test/expected/build/application.l/dest/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/application.l/dest/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/application.m/dest/Component-dbg.js b/packages/builder/test/expected/build/application.m/dest/Component-dbg.js new file mode 100644 index 00000000000..51a736bf6bc --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/Component-dbg.js @@ -0,0 +1,16 @@ +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/core/mvc/View"], (UIComponent, View) => { + "use strict"; + return UIComponent.extend("application.m.Component", { + metadata: { + manifest: "json", + interfaces: [ + "sap.ui.core.IAsyncContentCreation" + ] + }, + createContent() { + return View.create({ + viewName: "module:application/m/MyView" + }) + } + }); +}); diff --git a/packages/builder/test/expected/build/application.m/dest/Component-preload.js b/packages/builder/test/expected/build/application.m/dest/Component-preload.js new file mode 100644 index 00000000000..836b02aafd8 --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/Component-preload.js @@ -0,0 +1,8 @@ +//@ui5-bundle application/m/Component-preload.js +sap.ui.predefine("application/m/Component", ["sap/ui/core/UIComponent","sap/ui/core/mvc/View"],(e,t)=>{"use strict";return e.extend("application.m.Component",{metadata:{manifest:"json",interfaces:["sap.ui.core.IAsyncContentCreation"]},createContent(){return t.create({viewName:"module:application/m/MyView"})}})}); + +sap.ui.predefine("application/m/MyView", ["sap/ui/core/mvc/View","sap/m/Button"],(t,e)=>{"use strict";return t.extend("application.m.MyView",{async createContent(){return new e({id:this.createId("myButton"),text:"My Button"})}})}); +sap.ui.require.preload({ + "application/m/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"application.m","type":"application","applicationVersion":{"version":"1.0.0"}},"sap.ui5":{"contentDensities":{"compact":true,"cozy":true},"dependencies":{"minUI5Version":"1.108.0","libs":{"sap.ui.core":{},"sap.m":{}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.m/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.m/dest/Component-preload.js.map new file mode 100644 index 00000000000..c22da2fa56a --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","View","extend","metadata","manifest","interfaces","createContent","create","viewName"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,0BAA2B,wBAAyB,CAACC,EAAaC,KAChF,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,OACVC,WAAY,CACX,sCAGF,aAAAC,GACC,OAAOL,EAAKM,OAAO,CAClBC,SAAU,+BAEZ","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":["sap","ui","define","View","Button","extend","createContent","id","this","createId","text"],"sources":["MyView-dbg.js"],"mappings":"AAAA;AACAA,IAAIC,GAAGC,kCAAO,CACb,uBACA,gBACI,CAACC,EAAMC,KACX,aACA,OAAOD,EAAKE,OAAO,uBAAwB,CACzC,mBAAMC,GACP,OAAO,IAAIF,EAAO,CAChBG,GAAIC,KAAKC,SAAS,YAClBC,KAAM,aAEP","ignoreList":[],"sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/Component.js b/packages/builder/test/expected/build/application.m/dest/Component.js new file mode 100644 index 00000000000..7ce9f182256 --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent","sap/ui/core/mvc/View"],(e,t)=>{"use strict";return e.extend("application.m.Component",{metadata:{manifest:"json",interfaces:["sap.ui.core.IAsyncContentCreation"]},createContent(){return t.create({viewName:"module:application/m/MyView"})}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/Component.js.map b/packages/builder/test/expected/build/application.m/dest/Component.js.map new file mode 100644 index 00000000000..e116ae03b0a --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","View","extend","metadata","manifest","interfaces","createContent","create","viewName"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,0BAA2B,wBAAyB,CAACC,EAAaC,KAChF,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,OACVC,WAAY,CACX,sCAGF,aAAAC,GACC,OAAOL,EAAKM,OAAO,CAClBC,SAAU,+BAEZ","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/MyView-dbg.js b/packages/builder/test/expected/build/application.m/dest/MyView-dbg.js new file mode 100644 index 00000000000..0f137931685 --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/MyView-dbg.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +sap.ui.define([ + "sap/ui/core/mvc/View", + "sap/m/Button" + ], (View, Button) => { + "use strict"; + return View.extend("application.m.MyView", { + async createContent() { + return new Button({ + id: this.createId("myButton"), + text: "My Button" + }); + } + }); + }); diff --git a/packages/builder/test/expected/build/application.m/dest/MyView.js b/packages/builder/test/expected/build/application.m/dest/MyView.js new file mode 100644 index 00000000000..62cf30f4004 --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/MyView.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +sap.ui.define(["sap/ui/core/mvc/View","sap/m/Button"],(t,e)=>{"use strict";return t.extend("application.m.MyView",{async createContent(){return new e({id:this.createId("myButton"),text:"My Button"})}})}); +//# sourceMappingURL=MyView.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/MyView.js.map b/packages/builder/test/expected/build/application.m/dest/MyView.js.map new file mode 100644 index 00000000000..efb7eeb5bbc --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/MyView.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyView.js","names":["sap","ui","define","View","Button","extend","createContent","id","this","createId","text"],"sources":["MyView-dbg.js"],"mappings":";AACAA,IAAIC,GAAGC,OAAO,CACb,uBACA,gBACI,CAACC,EAAMC,KACX,aACA,OAAOD,EAAKE,OAAO,uBAAwB,CACzC,mBAAMC,GACP,OAAO,IAAIF,EAAO,CAChBG,GAAIC,KAAKC,SAAS,YAClBC,KAAM,aAEP","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.m/dest/index.html b/packages/builder/test/expected/build/application.m/dest/index.html new file mode 100644 index 00000000000..c60ee64599c --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/index.html @@ -0,0 +1,24 @@ + + + + + + Application M + + + + + +
    + + diff --git a/packages/builder/test/expected/build/application.m/dest/manifest.json b/packages/builder/test/expected/build/application.m/dest/manifest.json new file mode 100644 index 00000000000..32aa4794772 --- /dev/null +++ b/packages/builder/test/expected/build/application.m/dest/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "application.m", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + } + }, + "sap.ui5": { + "contentDensities": { + "compact": true, + "cozy": true + }, + "dependencies": { + "minUI5Version": "1.108.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/expected/build/application.n/dest/Component-dbg.js b/packages/builder/test/expected/build/application.n/dest/Component-dbg.js new file mode 100644 index 00000000000..0e58f2fbed9 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/Component-dbg.js @@ -0,0 +1,11 @@ +sap.ui.define(["sap/ui/core/UIComponent", "sap/m/Button", "application/n/MyModuleRequiringGlobalScope"], (UIComponent, Button) => { + "use strict"; + return UIComponent.extend("application.n.Component", { + metadata: { + manifest: "json" + }, + createContent() { + return new Button({text: magic.text}); + } + }); +}); diff --git a/packages/builder/test/expected/build/application.n/dest/Component.js b/packages/builder/test/expected/build/application.n/dest/Component.js new file mode 100644 index 00000000000..bb85f889fb2 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent","sap/m/Button","application/n/MyModuleRequiringGlobalScope"],(e,t)=>{"use strict";return e.extend("application.n.Component",{metadata:{manifest:"json"},createContent(){return new t({text:magic.text})}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/dest/Component.js.map b/packages/builder/test/expected/build/application.n/dest/Component.js.map new file mode 100644 index 00000000000..82a0523aec0 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","Button","extend","metadata","manifest","createContent","text","magic"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,0BAA2B,eAAgB,8CAA+C,CAACC,EAAaC,KACtH,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,QAEX,aAAAC,GACC,OAAO,IAAIJ,EAAO,CAACK,KAAMC,MAAMD,MAChC","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope-dbg.js b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope-dbg.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope-dbg.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..ec286f4ff52 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js @@ -0,0 +1,2 @@ +const magic={text:"It's magic!"}; +//# sourceMappingURL=MyModuleRequiringGlobalScope.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js.map b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js.map new file mode 100644 index 00000000000..2963452757c --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/MyModuleRequiringGlobalScope.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyModuleRequiringGlobalScope.js","names":["magic","text"],"sources":["MyModuleRequiringGlobalScope-dbg.js"],"mappings":"AAAA,MAAMA,MAAQ,CAACC,KAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/dest/index.html b/packages/builder/test/expected/build/application.n/dest/index.html new file mode 100644 index 00000000000..8798c3b8ddd --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/index.html @@ -0,0 +1,24 @@ + + + + + + Application N + + + + + +
    + + diff --git a/packages/builder/test/expected/build/application.n/dest/manifest.json b/packages/builder/test/expected/build/application.n/dest/manifest.json new file mode 100644 index 00000000000..1d7d445206e --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "application.n", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + } + }, + "sap.ui5": { + "contentDensities": { + "compact": true, + "cozy": true + }, + "dependencies": { + "minUI5Version": "1.108.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js new file mode 100644 index 00000000000..54dbb5e7d2b --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js @@ -0,0 +1,2 @@ +//@ui5-bundle sap-ui-custom-dbg.js +//# sourceMappingURL=sap-ui-custom-dbg.js.map diff --git a/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js.map b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js.map new file mode 100644 index 00000000000..7c185dcc4f6 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom-dbg.js","sections":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js new file mode 100644 index 00000000000..bfa90b72a12 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js @@ -0,0 +1,6 @@ +//@ui5-bundle sap-ui-custom.js +sap.ui.predefine("application/n/Component", ["sap/ui/core/UIComponent","sap/m/Button","application/n/MyModuleRequiringGlobalScope"],(e,t)=>{"use strict";return e.extend("application.n.Component",{metadata:{manifest:"json"},createContent(){return new t({text:magic.text})}})}); +sap.ui.require.preload({ + "application/n/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"application.n","type":"application","applicationVersion":{"version":"1.0.0"}},"sap.ui5":{"contentDensities":{"compact":true,"cozy":true},"dependencies":{"minUI5Version":"1.108.0","libs":{"sap.ui.core":{},"sap.m":{}}}}}' +}); +//# sourceMappingURL=sap-ui-custom.js.map diff --git a/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js.map b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js.map new file mode 100644 index 00000000000..35530554e2e --- /dev/null +++ b/packages/builder/test/expected/build/application.n/dest/resources/sap-ui-custom.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","Button","extend","metadata","manifest","createContent","text","magic"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,0BAA2B,eAAgB,8CAA+C,CAACC,EAAaC,KACtH,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,QAEX,aAAAC,GACC,OAAO,IAAIJ,EAAO,CAACK,KAAMC,MAAMD,MAChC","ignoreList":[],"sourceRoot":"application/n"}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/Component-dbg.js b/packages/builder/test/expected/build/application.n/legacy/Component-dbg.js new file mode 100644 index 00000000000..0e58f2fbed9 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/Component-dbg.js @@ -0,0 +1,11 @@ +sap.ui.define(["sap/ui/core/UIComponent", "sap/m/Button", "application/n/MyModuleRequiringGlobalScope"], (UIComponent, Button) => { + "use strict"; + return UIComponent.extend("application.n.Component", { + metadata: { + manifest: "json" + }, + createContent() { + return new Button({text: magic.text}); + } + }); +}); diff --git a/packages/builder/test/expected/build/application.n/legacy/Component.js b/packages/builder/test/expected/build/application.n/legacy/Component.js new file mode 100644 index 00000000000..bb85f889fb2 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent","sap/m/Button","application/n/MyModuleRequiringGlobalScope"],(e,t)=>{"use strict";return e.extend("application.n.Component",{metadata:{manifest:"json"},createContent(){return new t({text:magic.text})}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/Component.js.map b/packages/builder/test/expected/build/application.n/legacy/Component.js.map new file mode 100644 index 00000000000..82a0523aec0 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","Button","extend","metadata","manifest","createContent","text","magic"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,0BAA2B,eAAgB,8CAA+C,CAACC,EAAaC,KACtH,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,QAEX,aAAAC,GACC,OAAO,IAAIJ,EAAO,CAACK,KAAMC,MAAMD,MAChC","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope-dbg.js b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope-dbg.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope-dbg.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..ec286f4ff52 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js @@ -0,0 +1,2 @@ +const magic={text:"It's magic!"}; +//# sourceMappingURL=MyModuleRequiringGlobalScope.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js.map b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js.map new file mode 100644 index 00000000000..2963452757c --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/MyModuleRequiringGlobalScope.js.map @@ -0,0 +1 @@ +{"version":3,"file":"MyModuleRequiringGlobalScope.js","names":["magic","text"],"sources":["MyModuleRequiringGlobalScope-dbg.js"],"mappings":"AAAA,MAAMA,MAAQ,CAACC,KAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/index.html b/packages/builder/test/expected/build/application.n/legacy/index.html new file mode 100644 index 00000000000..8798c3b8ddd --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/index.html @@ -0,0 +1,24 @@ + + + + + + Application N + + + + + +
    + + diff --git a/packages/builder/test/expected/build/application.n/legacy/manifest.json b/packages/builder/test/expected/build/application.n/legacy/manifest.json new file mode 100644 index 00000000000..1d7d445206e --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "application.n", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + } + }, + "sap.ui5": { + "contentDensities": { + "compact": true, + "cozy": true + }, + "dependencies": { + "minUI5Version": "1.108.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js new file mode 100644 index 00000000000..54dbb5e7d2b --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js @@ -0,0 +1,2 @@ +//@ui5-bundle sap-ui-custom-dbg.js +//# sourceMappingURL=sap-ui-custom-dbg.js.map diff --git a/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js.map b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js.map new file mode 100644 index 00000000000..7c185dcc4f6 --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom-dbg.js","sections":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js new file mode 100644 index 00000000000..c84250dbfee --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js @@ -0,0 +1,7 @@ +//@ui5-bundle sap-ui-custom.js +sap.ui.predefine("application/n/Component", ["sap/ui/core/UIComponent","sap/m/Button","application/n/MyModuleRequiringGlobalScope"],(e,t)=>{"use strict";return e.extend("application.n.Component",{metadata:{manifest:"json"},createContent(){return new t({text:magic.text})}})}); +sap.ui.require.preload({ + "application/n/MyModuleRequiringGlobalScope.js":'const magic={text:"It\'s magic!"};\n', + "application/n/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"application.n","type":"application","applicationVersion":{"version":"1.0.0"}},"sap.ui5":{"contentDensities":{"compact":true,"cozy":true},"dependencies":{"minUI5Version":"1.108.0","libs":{"sap.ui.core":{},"sap.m":{}}}}}' +}); +//# sourceMappingURL=sap-ui-custom.js.map diff --git a/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js.map b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js.map new file mode 100644 index 00000000000..35530554e2e --- /dev/null +++ b/packages/builder/test/expected/build/application.n/legacy/resources/sap-ui-custom.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-custom.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","Button","extend","metadata","manifest","createContent","text","magic"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,0BAA2B,eAAgB,8CAA+C,CAACC,EAAaC,KACtH,aACA,OAAOD,EAAYE,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,QAEX,aAAAC,GACC,OAAO,IAAIJ,EAAO,CAACK,KAAMC,MAAMD,MAChC","ignoreList":[],"sourceRoot":"application/n"}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-custom.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/Component-preload.js b/packages/builder/test/expected/build/application.o/dest/Component-preload.js new file mode 100644 index 00000000000..bb7adb35c1f --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/Component-preload.js @@ -0,0 +1,10 @@ +//@ui5-bundle application/o/Component-preload.js +sap.ui.predefine("application/o/test", [],()=>{test(e=>{const s=e;console.log(s)});test()}); +sap.ui.require.preload({ + "application/o/i18n/i18n.properties":'welcome=Hello world', + "application/o/i18n/i18n_en.properties":'welcome=Hello EN world', + "application/o/i18n/i18n_en_US.properties":'welcome=Hello EN US world', + "application/o/i18n/i18n_en_US_saptrc.properties":'welcome=Hello EN US saptrc world', + "application/o/manifest.json":'{"_version":"1.22.0","sap.app":{"id":"application.o","type":"application","applicationVersion":{"version":"1.0.0"},"title":"{{title}}","i18n":{"bundleUrl":"i18n/i18n.properties","supportedLocales":["","en","en-US","en-US-x-saptrc"]}},"sap.ui5":{"models":{"i18n":{"type":"sap.ui.model.resource.ResourceModel","settings":{"bundleName":"application.o.i18n.i18n","supportedLocales":["","en","en-US","en-US-x-saptrc"]}},"i18n-ui5":{"type":"sap.ui.model.resource.ResourceModel","settings":{"bundleUrl":"ui5://application/o/i18n/i18n.properties","supportedLocales":["","en","en-US","en-US-x-saptrc"]}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/application.o/dest/Component-preload.js.map b/packages/builder/test/expected/build/application.o/dest/Component-preload.js.map new file mode 100644 index 00000000000..b70833ffa77 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,gCAAO,GACX,KACFC,KAAMC,IACL,MAAMC,EAAYD,EAClBE,QAAQC,IAAIF,KAEbF","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/i18n/i18n.properties b/packages/builder/test/expected/build/application.o/dest/i18n/i18n.properties new file mode 100644 index 00000000000..4fa9396ef8a --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/i18n/i18n.properties @@ -0,0 +1 @@ +welcome=Hello world \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en.properties b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en.properties new file mode 100644 index 00000000000..f03a373b0b8 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en.properties @@ -0,0 +1 @@ +welcome=Hello EN world \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US.properties b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US.properties new file mode 100644 index 00000000000..60d20479d61 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US.properties @@ -0,0 +1 @@ +welcome=Hello EN US world \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US_saptrc.properties b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US_saptrc.properties new file mode 100644 index 00000000000..88ea197de70 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/i18n/i18n_en_US_saptrc.properties @@ -0,0 +1 @@ +welcome=Hello EN US saptrc world \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/index.html b/packages/builder/test/expected/build/application.o/dest/index.html new file mode 100644 index 00000000000..991958fa5bf --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/index.html @@ -0,0 +1,11 @@ + + + + Application O + + + + + + diff --git a/packages/builder/test/expected/build/application.o/dest/manifest.json b/packages/builder/test/expected/build/application.o/dest/manifest.json new file mode 100644 index 00000000000..8ca7437348a --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/manifest.json @@ -0,0 +1,48 @@ +{ + "_version": "1.22.0", + "sap.app": { + "id": "application.o", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "i18n": { + "bundleUrl": "i18n/i18n.properties", + "supportedLocales": [ + "", + "en", + "en-US", + "en-US-x-saptrc" + ] + } + }, + "sap.ui5": { + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "application.o.i18n.i18n", + "supportedLocales": [ + "", + "en", + "en-US", + "en-US-x-saptrc" + ] + } + }, + "i18n-ui5": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleUrl": "ui5://application/o/i18n/i18n.properties", + "supportedLocales": [ + "", + "en", + "en-US", + "en-US-x-saptrc" + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/test-dbg.js b/packages/builder/test/expected/build/application.o/dest/test-dbg.js new file mode 100644 index 00000000000..399a1b6a147 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/test-dbg.js @@ -0,0 +1,8 @@ +sap.ui.define([ +], () => { + test((paramA) => { + const variableA = paramA; + console.log(variableA); + }) + test(); +}); diff --git a/packages/builder/test/expected/build/application.o/dest/test.js b/packages/builder/test/expected/build/application.o/dest/test.js new file mode 100644 index 00000000000..2a39c7bc3c9 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/test.js @@ -0,0 +1,2 @@ +sap.ui.define([],()=>{test(e=>{const s=e;console.log(s)});test()}); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/application.o/dest/test.js.map b/packages/builder/test/expected/build/application.o/dest/test.js.map new file mode 100644 index 00000000000..4e3c04b4ef7 --- /dev/null +++ b/packages/builder/test/expected/build/application.o/dest/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","names":["sap","ui","define","test","paramA","variableA","console","log"],"sources":["test-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,GACX,KACFC,KAAMC,IACL,MAAMC,EAAYD,EAClBE,QAAQC,IAAIF,KAEbF","ignoreList":[]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/Component-dbg.js" "b/packages/builder/test/expected/build/application.\303\270/dest/Component-dbg.js" new file mode 100644 index 00000000000..1036a390e61 --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/Component-dbg.js" @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.ø.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js" "b/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js" new file mode 100644 index 00000000000..b813d8e3ff6 --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js" @@ -0,0 +1,6 @@ +//@ui5-bundle application/ø/Component-preload.js +sap.ui.predefine("application/ø/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.ø.Component",{metadata:{manifest:"json"}})}); +sap.ui.require.preload({ + "application/ø/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.ø","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"},"sap.ui5":{"dependencies":{"minUI5Version":"1.73.2","libs":{"sap.ui.core":{}}}}}' +}); +//# sourceMappingURL=Component-preload.js.map diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js.map" "b/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js.map" new file mode 100644 index 00000000000..c1c1fa62ad1 --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/Component-preload.js.map" @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,qCAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/Component.js" "b/packages/builder/test/expected/build/application.\303\270/dest/Component.js" new file mode 100644 index 00000000000..8c43e47522a --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/Component.js" @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.ø.Component",{metadata:{manifest:"json"}})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/Component.js.map" "b/packages/builder/test/expected/build/application.\303\270/dest/Component.js.map" new file mode 100644 index 00000000000..54de04b1ee1 --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/Component.js.map" @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend","metadata","manifest"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACpDC,SAAU,CACTC,SAAU,SAGb","ignoreList":[]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/application.\303\270/dest/manifest.json" "b/packages/builder/test/expected/build/application.\303\270/dest/manifest.json" new file mode 100644 index 00000000000..435adbfeb7a --- /dev/null +++ "b/packages/builder/test/expected/build/application.\303\270/dest/manifest.json" @@ -0,0 +1,21 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.ø", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.73.2", + "libs": { + "sap.ui.core": {} + } + } + } +} diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/.library b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/.library new file mode 100644 index 00000000000..d6a612a9241 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + 1.0.0 + + Library D + + diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js new file mode 100644 index 00000000000..8af07c6008c --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ + +//# sourceMappingURL=empty.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js.map b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js.map new file mode 100644 index 00000000000..767ef9fbdc8 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/empty.js.map @@ -0,0 +1 @@ +{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js new file mode 100644 index 00000000000..8238eb084f2 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar="Old World";console.log("Hello "+topLevelVar); +//# sourceMappingURL=legacy.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js.map b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js.map new file mode 100644 index 00000000000..aa09f6e3c4c --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/legacy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"legacy.js","names":["topLevelVar","console","log"],"sources":["legacy-dbg.js"],"mappings":";;;AAGA,IAAIA,YAAc,YAClBC,QAAQC,IAAI,SAAWF"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js new file mode 100644 index 00000000000..916914b9860 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js @@ -0,0 +1,18 @@ +//@ui5-bundle library/d/library-preload.js +sap.ui.require.preload({ + "library/d/empty.js":function(){ +/*! + * Some fancy copyright + */ + +}, + "library/d/legacy.js":'/*!\n * Some fancy copyright\n */\nvar topLevelVar="Old World";console.log("Hello "+topLevelVar);\n', + "library/d/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"library.d","type":"library","embeds":[],"applicationVersion":{"version":"1.0.0"},"title":"Library D","description":"Library D","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":[]},"sap.ui5":{"dependencies":{"minUI5Version":"1.0","libs":{}},"library":{"i18n":false}}}', + "library/d/some.js":function(){ +/*! + * Some fancy copyright + */ +(function(){var o="World";console.log("Hello "+o)})(); +} +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js.map b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js.map new file mode 100644 index 00000000000..0ea9efa17a0 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[],"sourceRoot":""}},{"offset":{"line":11,"column":0},"map":{"version":3,"file":"some.js","names":["someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;CAGA,WACC,IAAIA,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF,EACvB,EAHD","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/manifest.json b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/manifest.json new file mode 100644 index 00000000000..c4f9b3baa72 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.d", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library D", + "description": "Library D", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js new file mode 100644 index 00000000000..75aba1e2383 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +(function(){var o="World";console.log("Hello "+o)})(); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js.map b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js.map new file mode 100644 index 00000000000..8f900826d79 --- /dev/null +++ b/packages/builder/test/expected/build/library.d-minified/preload/resources/library/d/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;CAGA,WACC,IAAIA,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF,EACvB,EAHD"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d-minified/preload/test-resources/library/d/Test.html b/packages/builder/test/expected/build/library.d-minified/preload/test-resources/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/.library b/packages/builder/test/expected/build/library.d/dest/resources/library/d/.library new file mode 100644 index 00000000000..d6a612a9241 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + 1.0.0 + + Library D + + diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty-dbg.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty-dbg.js new file mode 100644 index 00000000000..c82b694dc1c --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty-dbg.js @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */ diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js new file mode 100644 index 00000000000..8af07c6008c --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ + +//# sourceMappingURL=empty.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js.map b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js.map new file mode 100644 index 00000000000..767ef9fbdc8 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/empty.js.map @@ -0,0 +1 @@ +{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy-dbg.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy-dbg.js new file mode 100644 index 00000000000..dc97b0e1fdc --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar = "Old World"; +console.log('Hello ' + topLevelVar); diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js new file mode 100644 index 00000000000..8238eb084f2 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar="Old World";console.log("Hello "+topLevelVar); +//# sourceMappingURL=legacy.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js.map b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js.map new file mode 100644 index 00000000000..d8a842426f0 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/legacy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"legacy.js","names":["topLevelVar","console","log"],"sources":["legacy-dbg.js"],"mappings":";;;AAGA,IAAIA,YAAc,YAClBC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/library-dbg.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library-dbg.js new file mode 100644 index 00000000000..c024ed2d07e --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library-dbg.js @@ -0,0 +1,23 @@ +/* + * Some fancy copyright + */ + +sap.ui.define([ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "1.0.0", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js new file mode 100644 index 00000000000..f9910ae650d --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js @@ -0,0 +1,5 @@ +/* + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Lib","sap/ui/core/library"],function(e){"use strict";return e.init({name:"library.d",version:"1.0.0",dependencies:["sap.ui.core"],types:[],interfaces:[],controls:[],elements:[],noLibraryCSS:true})}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js.map b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js.map new file mode 100644 index 00000000000..911269e84c3 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Library","init","name","version","dependencies","types","interfaces","controls","elements","noLibraryCSS"],"sources":["library-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CACb,kBACA,uBACE,SAAUC,GACZ,aAEA,OAAOA,EAAQC,KAAK,CACnBC,KAAM,YACNC,QAAS,QACTC,aAAc,CACb,eAEDC,MAAO,GACPC,WAAY,GACZC,SAAU,GACVC,SAAU,GACVC,aAAc,MAEhB","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/manifest.json b/packages/builder/test/expected/build/library.d/dest/resources/library/d/manifest.json new file mode 100644 index 00000000000..facbd7f7dee --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/manifest.json @@ -0,0 +1,35 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.d", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library D", + "description": "Library D", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false, + "css": false, + "content": { + "controls": [], + "elements": [], + "types": [], + "interfaces": [] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/some-dbg.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some-dbg.js new file mode 100644 index 00000000000..3699e19874e --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some-dbg.js @@ -0,0 +1,8 @@ +/*! + * Some fancy copyright + */ + +sap.ui.define(["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js new file mode 100644 index 00000000000..8d85629bba9 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["./library"],l=>{var o="World";console.log("Hello "+o)}); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js.map b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js.map new file mode 100644 index 00000000000..9daafa1872a --- /dev/null +++ b/packages/builder/test/expected/build/library.d/dest/resources/library/d/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["sap","ui","define","_library","someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,CAAC,aAAeC,IAC7B,IAAIC,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/dest/test-resources/library/d/Test.html b/packages/builder/test/expected/build/library.d/dest/test-resources/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/.library b/packages/builder/test/expected/build/library.d/preload/resources/library/d/.library new file mode 100644 index 00000000000..53c2d14c9d6 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + ${version} + + Library D + + diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/empty.js b/packages/builder/test/expected/build/library.d/preload/resources/library/d/empty.js new file mode 100644 index 00000000000..117426a8389 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/empty.js @@ -0,0 +1,3 @@ +/*! + * ${copyright} + */ diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/legacy.js b/packages/builder/test/expected/build/library.d/preload/resources/library/d/legacy.js new file mode 100644 index 00000000000..d4a7b26fdf4 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +var topLevelVar = "Old World"; +console.log('Hello ' + topLevelVar); diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js new file mode 100644 index 00000000000..4099e45b787 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js @@ -0,0 +1,41 @@ +//@ui5-bundle library/d/library-preload.js +/* + * ${copyright} + */ + +sap.ui.predefine("library/d/library", [ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "${version}", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); +/*! + * ${copyright} + */ + +sap.ui.predefine("library/d/some", ["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); +sap.ui.require.preload({ + "library/d/empty.js":function(){ +/*! + * ${copyright} + */ +}, + "library/d/legacy.js":'/*!\n * ${copyright}\n */\nvar topLevelVar = "Old World";\nconsole.log(\'Hello \' + topLevelVar);\n' +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js.map b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js.map new file mode 100644 index 00000000000..8e99bab16a0 --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["library.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":24,"column":0},"map":{"version":3,"names":[],"sources":["some.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":32,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":34,"column":0},"map":{"version":3,"names":[],"sources":["empty.js"],"mappings":"AAAA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/library.js b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library.js new file mode 100644 index 00000000000..678cb80349b --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/library.js @@ -0,0 +1,23 @@ +/* + * ${copyright} + */ + +sap.ui.define([ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "${version}", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); diff --git a/packages/builder/test/expected/build/library.d/preload/resources/library/d/some.js b/packages/builder/test/expected/build/library.d/preload/resources/library/d/some.js new file mode 100644 index 00000000000..7c7022e462c --- /dev/null +++ b/packages/builder/test/expected/build/library.d/preload/resources/library/d/some.js @@ -0,0 +1,8 @@ +/*! + * ${copyright} + */ + +sap.ui.define(["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); diff --git a/packages/builder/test/expected/build/library.d/preload/test-resources/library/d/Test.html b/packages/builder/test/expected/build/library.d/preload/test-resources/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.e/build-manifest/.ui5/build-manifest.json b/packages/builder/test/expected/build/library.e/build-manifest/.ui5/build-manifest.json new file mode 100644 index 00000000000..e2beeb54d33 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/.ui5/build-manifest.json @@ -0,0 +1 @@ +// File content is not statically compared diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/.library b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/.library new file mode 100644 index 00000000000..54b1de383b3 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/.library @@ -0,0 +1,13 @@ + + + + library.e + SAP SE + UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + 1.0.0 + + Library E + + diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-dbg.js b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-dbg.js new file mode 100644 index 00000000000..e45381dafbd --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-dbg.js @@ -0,0 +1,8 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log('HelloWorld'); + +// 1.0.0 diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js new file mode 100644 index 00000000000..224e42018bd --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js @@ -0,0 +1,21 @@ +//@ui5-bundle library/e/library-preload.js +sap.ui.require.preload({ + "library/e/library.js":function(){ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +}, + "library/e/manifest.json":'{"_version":"1.1.0","sap.app":{"id":"library.e","type":"library","embeds":[],"applicationVersion":{"version":"1.0.0"},"title":"Library E","description":"Library E","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":[]},"sap.ui5":{"dependencies":{"minUI5Version":"1.0","libs":{}},"library":{"i18n":false}}}', + "library/e/some.js":function(){ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +} +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js.map b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js.map new file mode 100644 index 00000000000..27679b4e473 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":"AAAA;;;;;AAKAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":12,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;;;AAKAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js new file mode 100644 index 00000000000..64854027843 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js @@ -0,0 +1,7 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js.map b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js.map new file mode 100644 index 00000000000..68f9502ed16 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":";;;;;AAKAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/manifest.json b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/manifest.json new file mode 100644 index 00000000000..6ff551fd1fb --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.1.0", + "sap.app": { + "id": "library.e", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library E", + "description": "Library E", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some-dbg.js b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some-dbg.js new file mode 100644 index 00000000000..dc9e1fc914e --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some-dbg.js @@ -0,0 +1,6 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js new file mode 100644 index 00000000000..36245f9001f --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js @@ -0,0 +1,7 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js.map b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js.map new file mode 100644 index 00000000000..364aa27d264 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/resources/library/e/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":";;;;;AAKAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.css b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.css new file mode 100644 index 00000000000..0c074af6218 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.css @@ -0,0 +1,6 @@ +/* +UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. +1.0.0 +*/ diff --git a/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.html b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.html new file mode 100644 index 00000000000..5a1a4014b7a --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.html @@ -0,0 +1,6 @@ + diff --git a/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.js b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.js new file mode 100644 index 00000000000..0c074af6218 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/build-manifest/test-resources/library/e/Test.js @@ -0,0 +1,6 @@ +/* +UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. +1.0.0 +*/ diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/.library b/packages/builder/test/expected/build/library.e/dest/resources/library/e/.library new file mode 100644 index 00000000000..54b1de383b3 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/.library @@ -0,0 +1,13 @@ + + + + library.e + SAP SE + UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + 1.0.0 + + Library E + + diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/library-dbg.js b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library-dbg.js new file mode 100644 index 00000000000..e45381dafbd --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library-dbg.js @@ -0,0 +1,8 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log('HelloWorld'); + +// 1.0.0 diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js new file mode 100644 index 00000000000..64854027843 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js @@ -0,0 +1,7 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js.map b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js.map new file mode 100644 index 00000000000..68f9502ed16 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":";;;;;AAKAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/manifest.json b/packages/builder/test/expected/build/library.e/dest/resources/library/e/manifest.json new file mode 100644 index 00000000000..6ff551fd1fb --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.1.0", + "sap.app": { + "id": "library.e", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library E", + "description": "Library E", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/some-dbg.js b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some-dbg.js new file mode 100644 index 00000000000..dc9e1fc914e --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some-dbg.js @@ -0,0 +1,6 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js new file mode 100644 index 00000000000..36245f9001f --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js @@ -0,0 +1,7 @@ +/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js.map b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js.map new file mode 100644 index 00000000000..364aa27d264 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/resources/library/e/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":";;;;;AAKAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.css b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.css new file mode 100644 index 00000000000..0c074af6218 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.css @@ -0,0 +1,6 @@ +/* +UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. +1.0.0 +*/ diff --git a/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.html b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.html new file mode 100644 index 00000000000..5a1a4014b7a --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.html @@ -0,0 +1,6 @@ + diff --git a/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.js b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.js new file mode 100644 index 00000000000..0c074af6218 --- /dev/null +++ b/packages/builder/test/expected/build/library.e/dest/test-resources/library/e/Test.js @@ -0,0 +1,6 @@ +/* +UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. +1.0.0 +*/ diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/.ui5/build-manifest.json b/packages/builder/test/expected/build/library.h/dest-resources-json/.ui5/build-manifest.json new file mode 100644 index 00000000000..e2beeb54d33 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/.ui5/build-manifest.json @@ -0,0 +1 @@ +// File content is not statically compared diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/.library b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/.library new file mode 100644 index 00000000000..9084728e818 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/.library @@ -0,0 +1,19 @@ + + + + library.h + SAP SE + Some fancy copyright + 1.0.0 + + Library H + + + + + + + + + + diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js new file mode 100644 index 00000000000..72f30397ed7 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js @@ -0,0 +1,11 @@ +//@ui5-bundle library/h/components/Component-preload.js +sap.ui.predefine("library/h/components/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +sap.ui.require.preload({ + "library/h/components/TodoComponent.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +} +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js.map new file mode 100644 index 00000000000..b1837ff2ef3 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,4CAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"file":"TodoComponent.js","names":["console","log"],"sources":["TodoComponent-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent-dbg.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js new file mode 100644 index 00000000000..f58e632b334 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" File "); +//# sourceMappingURL=TodoComponent.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js.map new file mode 100644 index 00000000000..77dafc98179 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TodoComponent.js","names":["console","log"],"sources":["TodoComponent-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json new file mode 100644 index 00000000000..b204e77aeee --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json @@ -0,0 +1,184 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Component-dbg.js", + "module": "library/h/components/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component-preload.js", + "module": "library/h/components/Component-preload.js", + "size": 394, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/Component.js", + "library/h/components/TodoComponent.js" + ] + }, + { + "name": "Component-preload.js.map", + "size": 735, + "isDebug": true + }, + { + "name": "Component.js", + "module": "library/h/components/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "TodoComponent-dbg.js", + "module": "library/h/components/TodoComponent.js", + "size": 55, + "format": "raw", + "isDebug": true + }, + { + "name": "TodoComponent.js", + "module": "library/h/components/TodoComponent.js", + "size": 96, + "format": "raw" + }, + { + "name": "TodoComponent.js.map", + "size": 149, + "isDebug": true + }, + { + "name": "resources.json", + "size": 3883 + }, + { + "name": "subcomponent1/Component-dbg.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent1/Component-preload.js", + "module": "library/h/components/subcomponent1/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent1/Component.js" + ] + }, + { + "name": "subcomponent1/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "subcomponent1/Component.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent1/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "subcomponent2/Component-dbg.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent2/Component-preload.js", + "module": "library/h/components/subcomponent2/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent2/Component.js" + ] + }, + { + "name": "subcomponent2/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "subcomponent2/Component.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent2/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "subcomponent3/Component-dbg.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent3/Component-preload.js", + "module": "library/h/components/subcomponent3/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent3/Component.js" + ] + }, + { + "name": "subcomponent3/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "subcomponent3/Component.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "subcomponent3/Component.js.map", + "size": 235, + "isDebug": true + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js new file mode 100644 index 00000000000..b048cbf6c90 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent1/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent1/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json new file mode 100644 index 00000000000..919c1a56261 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json @@ -0,0 +1,48 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Component-dbg.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component-preload.js", + "module": "library/h/components/subcomponent1/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent1/Component.js" + ] + }, + { + "name": "Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "Component.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "resources.json", + "size": 903 + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js new file mode 100644 index 00000000000..af949597401 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent2/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent2/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json new file mode 100644 index 00000000000..5481bb78def --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json @@ -0,0 +1,48 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Component-dbg.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component-preload.js", + "module": "library/h/components/subcomponent2/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent2/Component.js" + ] + }, + { + "name": "Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "Component.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "resources.json", + "size": 903 + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js new file mode 100644 index 00000000000..daa48b615b5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent3/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent3/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json new file mode 100644 index 00000000000..fbf6bce6a83 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json @@ -0,0 +1,48 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Component-dbg.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component-preload.js", + "module": "library/h/components/subcomponent3/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent3/Component.js" + ] + }, + { + "name": "Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "Component.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "resources.json", + "size": 903 + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js new file mode 100644 index 00000000000..b9836166675 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle-dbg.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customBundle-dbg.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js.map new file mode 100644 index 00000000000..bbadd535226 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle-dbg.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js new file mode 100644 index 00000000000..336248ee259 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" Library "); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js.map new file mode 100644 index 00000000000..87449f0907e --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;;AAIAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library-dbg.designtime.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library-dbg.designtime.js new file mode 100644 index 00000000000..34069d6ac28 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library-dbg.designtime.js @@ -0,0 +1,14 @@ +/*! + * Some fancy copyright + */ + +/** + * designtime and global export + */ +var myexport = (function() { + + "use strict"; + + String("asd"); + +}()); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js new file mode 100644 index 00000000000..530ba01e115 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var myexport=function(){"use strict";String("asd")}(); +//# sourceMappingURL=library.designtime.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js.map new file mode 100644 index 00000000000..0b6200d04fa --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.designtime.js","names":["myexport","String"],"sources":["library-dbg.designtime.js"],"mappings":";;;AAOA,IAAIA,SAAY,WAEf,aAEAC,OAAO,MAER,CANe","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file-dbg.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js new file mode 100644 index 00000000000..2fe4c2aae6e --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" File "); +//# sourceMappingURL=file.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js.map new file mode 100644 index 00000000000..9044ec7c813 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js.map @@ -0,0 +1 @@ +{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library-dbg.js new file mode 100644 index 00000000000..c9d3cbc0cca --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Library '); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js new file mode 100644 index 00000000000..ce760e5cbae --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" Library "); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js.map new file mode 100644 index 00000000000..9d4c7ac7ac6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json new file mode 100644 index 00000000000..808d3df9064 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json @@ -0,0 +1,34 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.h", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library H", + "description": "Library H", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false, + "content": { + "controls": [], + "elements": [], + "types": [], + "interfaces": [] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not-dbg.js new file mode 100644 index 00000000000..d61495954b5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Not including '); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js new file mode 100644 index 00000000000..75a862b1412 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=not.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js.map new file mode 100644 index 00000000000..ef4d6a63904 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js.map @@ -0,0 +1 @@ +{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json new file mode 100644 index 00000000000..5895e04d4d9 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json @@ -0,0 +1,365 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": ".library", + "size": 473 + }, + { + "name": "components/Component-dbg.js", + "module": "library/h/components/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/Component-preload.js", + "module": "library/h/components/Component-preload.js", + "size": 394, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/Component.js", + "library/h/components/TodoComponent.js" + ] + }, + { + "name": "components/Component-preload.js.map", + "size": 735, + "isDebug": true + }, + { + "name": "components/Component.js", + "module": "library/h/components/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "components/TodoComponent-dbg.js", + "module": "library/h/components/TodoComponent.js", + "size": 55, + "format": "raw", + "isDebug": true + }, + { + "name": "components/TodoComponent.js", + "module": "library/h/components/TodoComponent.js", + "size": 96, + "format": "raw" + }, + { + "name": "components/TodoComponent.js.map", + "size": 149, + "isDebug": true + }, + { + "name": "components/subcomponent1/Component-dbg.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent1/Component-preload.js", + "module": "library/h/components/subcomponent1/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent1/Component.js" + ] + }, + { + "name": "components/subcomponent1/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "components/subcomponent1/Component.js", + "module": "library/h/components/subcomponent1/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent1/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "components/subcomponent2/Component-dbg.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent2/Component-preload.js", + "module": "library/h/components/subcomponent2/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent2/Component.js" + ] + }, + { + "name": "components/subcomponent2/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "components/subcomponent2/Component.js", + "module": "library/h/components/subcomponent2/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent2/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "components/subcomponent3/Component-dbg.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 146, + "isDebug": true, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent3/Component-preload.js", + "module": "library/h/components/subcomponent3/Component-preload.js", + "size": 283, + "merged": true, + "required": [ + "sap/ui/core/UIComponent.js" + ], + "included": [ + "library/h/components/subcomponent3/Component.js" + ] + }, + { + "name": "components/subcomponent3/Component-preload.js.map", + "size": 326, + "isDebug": true + }, + { + "name": "components/subcomponent3/Component.js", + "module": "library/h/components/subcomponent3/Component.js", + "size": 153, + "required": [ + "sap/ui/core/UIComponent.js" + ] + }, + { + "name": "components/subcomponent3/Component.js.map", + "size": 235, + "isDebug": true + }, + { + "name": "customBundle-dbg.js", + "module": "library/h/customBundle.js", + "size": 548, + "isDebug": true, + "merged": true, + "required": [ + "library/h/manifest.json" + ], + "included": [ + "library/h/file.js", + "library/h/library.js", + "library/h/some.js", + "library/h/other.js", + "library/h/not.js" + ] + }, + { + "name": "customBundle-dbg.js.map", + "size": 839, + "isDebug": true + }, + { + "name": "customBundle.js", + "module": "library/h/customBundle.js", + "size": 540, + "merged": true, + "required": [ + "library/h/manifest.json" + ], + "included": [ + "library/h/file.js", + "library/h/library.js", + "library/h/some.js", + "library/h/other.js", + "library/h/not.js" + ] + }, + { + "name": "customBundle.js.map", + "size": 1017, + "isDebug": true + }, + { + "name": "designtime/library-dbg.designtime.js", + "module": "library/h/designtime/library.designtime.js", + "size": 142, + "requiresTopLevelScope": true, + "exposedGlobalNames": [ + "myexport" + ], + "format": "raw", + "isDebug": true, + "designtime": true + }, + { + "name": "designtime/library.designtime.js", + "module": "library/h/designtime/library.designtime.js", + "size": 133, + "requiresTopLevelScope": true, + "exposedGlobalNames": [ + "myexport" + ], + "format": "raw", + "designtime": true + }, + { + "name": "designtime/library.designtime.js.map", + "size": 188, + "isDebug": true, + "designtime": true + }, + { + "name": "file-dbg.js", + "module": "library/h/file.js", + "size": 55, + "format": "raw", + "isDebug": true + }, + { + "name": "file.js", + "module": "library/h/file.js", + "size": 87, + "format": "raw" + }, + { + "name": "file.js.map", + "size": 131, + "isDebug": true + }, + { + "name": "i18n/messagebundle.properties", + "module": "library/h/i18n/messagebundle.properties", + "size": 3, + "locale": "", + "raw": "i18n/messagebundle.properties" + }, + { + "name": "i18n/messagebundle_en.properties", + "module": "library/h/i18n/messagebundle_en.properties", + "size": 3, + "locale": "en", + "raw": "i18n/messagebundle.properties" + }, + { + "name": "library-dbg.js", + "module": "library/h/library.js", + "size": 58, + "format": "raw", + "isDebug": true, + "required": [ + "library/h/manifest.json" + ] + }, + { + "name": "library.js", + "module": "library/h/library.js", + "size": 93, + "format": "raw", + "required": [ + "library/h/manifest.json" + ] + }, + { + "name": "library.js.map", + "size": 137, + "isDebug": true + }, + { + "name": "manifest.json", + "module": "library/h/manifest.json", + "size": 614 + }, + { + "name": "not-dbg.js", + "module": "library/h/not.js", + "size": 64, + "format": "raw", + "isDebug": true + }, + { + "name": "not.js", + "module": "library/h/not.js", + "size": 95, + "format": "raw" + }, + { + "name": "not.js.map", + "size": 129, + "isDebug": true + }, + { + "name": "resources.json", + "size": 7540 + }, + { + "name": "some-dbg.js", + "module": "library/h/some.js", + "size": 100, + "format": "raw", + "isDebug": true, + "merged": true, + "included": [ + "library/h/other.js" + ] + }, + { + "name": "some.js", + "module": "library/h/some.js", + "size": 132, + "format": "raw", + "merged": true, + "included": [ + "library/h/other.js" + ] + }, + { + "name": "some.js.map", + "size": 132, + "isDebug": true + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some-dbg.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some-dbg.js new file mode 100644 index 00000000000..e03abda4d47 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js new file mode 100644 index 00000000000..0c5f5d3e325 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js @@ -0,0 +1,6 @@ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js.map b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js.map new file mode 100644 index 00000000000..0cabd17e7ec --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":";;;;AAIAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest-resources-json/test-resources/library/h/Test.html b/packages/builder/test/expected/build/library.h/dest-resources-json/test-resources/library/h/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/.library b/packages/builder/test/expected/build/library.h/dest/resources/library/h/.library new file mode 100644 index 00000000000..9084728e818 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/.library @@ -0,0 +1,19 @@ + + + + library.h + SAP SE + Some fancy copyright + 1.0.0 + + Library H + + + + + + + + + + diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js new file mode 100644 index 00000000000..72f30397ed7 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js @@ -0,0 +1,11 @@ +//@ui5-bundle library/h/components/Component-preload.js +sap.ui.predefine("library/h/components/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +sap.ui.require.preload({ + "library/h/components/TodoComponent.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +} +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js.map new file mode 100644 index 00000000000..b1837ff2ef3 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,4CAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}},{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"file":"TodoComponent.js","names":["console","log"],"sources":["TodoComponent-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent-dbg.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js new file mode 100644 index 00000000000..f58e632b334 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" File "); +//# sourceMappingURL=TodoComponent.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js.map new file mode 100644 index 00000000000..77dafc98179 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/TodoComponent.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TodoComponent.js","names":["console","log"],"sources":["TodoComponent-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js new file mode 100644 index 00000000000..b048cbf6c90 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent1/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent1/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent1/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js new file mode 100644 index 00000000000..af949597401 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent2/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent2/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent2/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-dbg.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-dbg.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js new file mode 100644 index 00000000000..daa48b615b5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js @@ -0,0 +1,3 @@ +//@ui5-bundle library/h/components/subcomponent3/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent3/Component", ["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js.map new file mode 100644 index 00000000000..57e8e0b4ff8 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,0DAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js new file mode 100644 index 00000000000..04d85498427 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js @@ -0,0 +1,2 @@ +sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})}); +//# sourceMappingURL=Component.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js.map new file mode 100644 index 00000000000..baeeabdc554 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/components/subcomponent3/Component.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component.js","names":["sap","ui","define","UIComponent","extend"],"sources":["Component-dbg.js"],"mappings":"AAAAA,IAAIC,GAAGC,OAAO,CAAC,2BAA4B,SAASC,GACnD,aACA,OAAOA,EAAYC,OAAO,0BAA2B,CACrD,EACD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js new file mode 100644 index 00000000000..b9836166675 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle-dbg.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customBundle-dbg.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js.map new file mode 100644 index 00000000000..bbadd535226 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle-dbg.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js new file mode 100644 index 00000000000..336248ee259 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" Library "); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js.map new file mode 100644 index 00000000000..87449f0907e --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;;AAIAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library-dbg.designtime.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library-dbg.designtime.js new file mode 100644 index 00000000000..34069d6ac28 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library-dbg.designtime.js @@ -0,0 +1,14 @@ +/*! + * Some fancy copyright + */ + +/** + * designtime and global export + */ +var myexport = (function() { + + "use strict"; + + String("asd"); + +}()); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js new file mode 100644 index 00000000000..530ba01e115 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var myexport=function(){"use strict";String("asd")}(); +//# sourceMappingURL=library.designtime.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js.map new file mode 100644 index 00000000000..0b6200d04fa --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.designtime.js","names":["myexport","String"],"sources":["library-dbg.designtime.js"],"mappings":";;;AAOA,IAAIA,SAAY,WAEf,aAEAC,OAAO,MAER,CANe","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/file-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file-dbg.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js new file mode 100644 index 00000000000..2fe4c2aae6e --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" File "); +//# sourceMappingURL=file.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js.map new file mode 100644 index 00000000000..9044ec7c813 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/file.js.map @@ -0,0 +1 @@ +{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties b/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties b/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/library-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library-dbg.js new file mode 100644 index 00000000000..c9d3cbc0cca --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Library '); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js new file mode 100644 index 00000000000..ce760e5cbae --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" Library "); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js.map new file mode 100644 index 00000000000..9d4c7ac7ac6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/manifest.json b/packages/builder/test/expected/build/library.h/dest/resources/library/h/manifest.json new file mode 100644 index 00000000000..808d3df9064 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/manifest.json @@ -0,0 +1,34 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.h", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library H", + "description": "Library H", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false, + "content": { + "controls": [], + "elements": [], + "types": [], + "interfaces": [] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/not-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not-dbg.js new file mode 100644 index 00000000000..d61495954b5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not-dbg.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Not including '); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js new file mode 100644 index 00000000000..75a862b1412 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=not.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js.map new file mode 100644 index 00000000000..ef4d6a63904 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/not.js.map @@ -0,0 +1 @@ +{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/some-dbg.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some-dbg.js new file mode 100644 index 00000000000..e03abda4d47 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js new file mode 100644 index 00000000000..0c5f5d3e325 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js @@ -0,0 +1,6 @@ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js.map b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js.map new file mode 100644 index 00000000000..0cabd17e7ec --- /dev/null +++ b/packages/builder/test/expected/build/library.h/dest/resources/library/h/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":";;;;AAIAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/dest/test-resources/library/h/Test.html b/packages/builder/test/expected/build/library.h/dest/test-resources/library/h/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/.library b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/.library new file mode 100644 index 00000000000..9084728e818 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/.library @@ -0,0 +1,19 @@ + + + + library.h + SAP SE + Some fancy copyright + 1.0.0 + + Library H + + + + + + + + + + diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js new file mode 100644 index 00000000000..f036cd6d737 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js @@ -0,0 +1,15 @@ +//@ui5-bundle library/h/components/Component-preload.js +sap.ui.predefine("library/h/components/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); +sap.ui.require.preload({ + "library/h/components/TodoComponent.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +} +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js.map new file mode 100644 index 00000000000..eea8b77e0a0 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":[],"sources":["Component-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":8,"column":0},"map":{"version":3,"names":[],"sources":["TodoComponent.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/TodoComponent.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/TodoComponent.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/TodoComponent.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js new file mode 100644 index 00000000000..3e7256d36f9 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js @@ -0,0 +1,7 @@ +//@ui5-bundle library/h/components/subcomponent1/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent1/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js.map new file mode 100644 index 00000000000..7743e6a2d5a --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent1/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js new file mode 100644 index 00000000000..7506670682e --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js @@ -0,0 +1,7 @@ +//@ui5-bundle library/h/components/subcomponent2/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent2/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js.map new file mode 100644 index 00000000000..7743e6a2d5a --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent2/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js new file mode 100644 index 00000000000..3e05261fe90 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js @@ -0,0 +1,7 @@ +//@ui5-bundle library/h/components/subcomponent3/Component-preload.js +sap.ui.predefine("library/h/components/subcomponent3/Component", ["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); +//# sourceMappingURL=Component-preload.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js.map new file mode 100644 index 00000000000..7743e6a2d5a --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Component-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Component.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/components/subcomponent3/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js new file mode 100644 index 00000000000..b9836166675 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle-dbg.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customBundle-dbg.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js.map new file mode 100644 index 00000000000..1a09579daca --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle-dbg.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js new file mode 100644 index 00000000000..380250bbea3 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/h/customBundle.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customBundle.js.map diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js.map b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js.map new file mode 100644 index 00000000000..bd534655bbd --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/customBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customBundle.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/designtime/library.designtime.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/designtime/library.designtime.js new file mode 100644 index 00000000000..34069d6ac28 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/designtime/library.designtime.js @@ -0,0 +1,14 @@ +/*! + * Some fancy copyright + */ + +/** + * designtime and global export + */ +var myexport = (function() { + + "use strict"; + + String("asd"); + +}()); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/file.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/file.js new file mode 100644 index 00000000000..ab9d806f65c --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/file.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' File '); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle.properties b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle_en.properties b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle_en.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/i18n/messagebundle_en.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/library.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/library.js new file mode 100644 index 00000000000..c9d3cbc0cca --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/library.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Library '); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/manifest.json b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/manifest.json new file mode 100644 index 00000000000..808d3df9064 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/manifest.json @@ -0,0 +1,34 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.h", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library H", + "description": "Library H", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false, + "content": { + "controls": [], + "elements": [], + "types": [], + "interfaces": [] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/not.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/not.js new file mode 100644 index 00000000000..d61495954b5 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/not.js @@ -0,0 +1,4 @@ +/*! + * Some fancy copyright + */ +console.log(' Not including '); diff --git a/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/some.js b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/some.js new file mode 100644 index 00000000000..e03abda4d47 --- /dev/null +++ b/packages/builder/test/expected/build/library.h/no-minify/resources/library/h/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); diff --git a/packages/builder/test/expected/build/library.h/no-minify/test-resources/library/h/Test.html b/packages/builder/test/expected/build/library.h/no-minify/test-resources/library/h/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/.ui5/build-manifest.json b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/.ui5/build-manifest.json new file mode 100644 index 00000000000..e2beeb54d33 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/.ui5/build-manifest.json @@ -0,0 +1 @@ +// File content is not statically compared diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/.library b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/.library new file mode 100644 index 00000000000..acfb88eed61 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/.library @@ -0,0 +1,44 @@ + + + + library.i + SAP SE + Some fancy copyright + 1.0.0 + + {{title}} + {{description}} + + + + sap.ui.core + + + library.d + true + + + + + + i18nf/custommessagebundle.properties + false + + sap_belize + sap_belize_plus + sap_belize_hcw + sap_belize_hcb + + + + + F0042 + fancycomponent + + + + NOT-ME + + + + diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js new file mode 100644 index 00000000000..bf457f50e5a --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/i/customLibraryHBundle-dbg.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customLibraryHBundle-dbg.js.map diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js.map new file mode 100644 index 00000000000..8f3910f6f90 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customLibraryHBundle-dbg.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customLibraryHBundle-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js new file mode 100644 index 00000000000..39fabf84473 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/i/customLibraryHBundle.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" Library "); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=customLibraryHBundle.js.map diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js.map new file mode 100644 index 00000000000..c726fa3827d --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/customLibraryHBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customLibraryHBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customLibraryHBundle.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":9,"column":0},"map":{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":15,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;;AAIAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":23,"column":0},"map":{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/i18nf/custommessagebundle.properties b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/i18nf/custommessagebundle.properties new file mode 100644 index 00000000000..dbb825dabd8 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/i18nf/custommessagebundle.properties @@ -0,0 +1 @@ +mytext=Hello \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-dbg.js b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-dbg.js new file mode 100644 index 00000000000..48a19acb752 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-dbg.js @@ -0,0 +1,38 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], function(Core) { + + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.i", + version: "1.0.0", + dependencies : ["sap.ui.core"], + types: [ + "library.i.ButtonType", + "library.i.DialogType", + ], + interfaces: [ + "library.i.IContent", + ], + controls: [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + elements: [ + "library.i.MenuItem" + ], + }); + + return thisLib; + +}); diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js new file mode 100644 index 00000000000..793378555eb --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle library/i/library-preload.js +/*! + * Some fancy copyright + */ +sap.ui.predefine("library/i/library", ["sap/ui/core/Core"],function(i){"use strict";sap.ui.getCore().initLibrary({name:"library.i",version:"1.0.0",dependencies:["sap.ui.core"],types:["library.i.ButtonType","library.i.DialogType"],interfaces:["library.i.IContent"],controls:["library.i.Button","library.i.CheckBox","library.i.Dialog","library.i.Input","library.i.Label","library.i.Link","library.i.Menu","library.i.Text"],elements:["library.i.MenuItem"]});return thisLib}); +sap.ui.require.preload({ + "library/i/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"library.i","type":"library","embeds":[],"i18n":{"bundleUrl":"i18nf/custommessagebundle.properties","supportedLocales":[""],"fallbackLocale":""},"applicationVersion":{"version":"1.0.0"},"title":"{{title}}","description":"{{description}}","ach":"NOT-ME","resources":"resources.json","offline":false,"openSourceComponents":[{"name":"D3.js","packagedWithMySelf":false},{"name":"Hammer.js","packagedWithMySelf":true,"version":"2.0.4"}]},"sap.ui":{"technology":"UI5","deviceTypes":{"desktop":true,"tablet":true,"phone":true},"supportedThemes":[]},"sap.ui5":{"dependencies":{"minUI5Version":"1.0","libs":{"sap.ui.core":{"minVersion":"1.0.0"},"library.d":{"lazy":true}}},"contentDensities":{"cozy":true,"compact":false},"library":{"i18n":false,"content":{"controls":["library.i.Button","library.i.CheckBox","library.i.Dialog","library.i.Input","library.i.Label","library.i.Link","library.i.Menu","library.i.Text"],"elements":["library.i.MenuItem"],"types":["library.i.ButtonType","library.i.DialogType"],"interfaces":["library.i.IContent"]}}},"sap.fiori":{"registrationIds":["F0042"],"archeType":"fancycomponent"}}' +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js.map new file mode 100644 index 00000000000..47e95366f6c --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","Core","getCore","initLibrary","name","version","dependencies","types","interfaces","controls","elements","thisLib"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,IAAIC,GAAGC,+BAAO,CACb,oBACE,SAASC,GAEX,aAEAH,IAAIC,GAAGG,UAAUC,YAAY,CAC5BC,KAAO,YACPC,QAAS,QACTC,aAAe,CAAC,eAChBC,MAAO,CACN,uBACA,wBAEDC,WAAY,CACX,sBAEDC,SAAU,CACT,mBACA,qBACA,mBACA,kBACA,kBACA,iBACA,iBACA,kBAEDC,SAAU,CACT,wBAIF,OAAOC,OAER","ignoreList":[],"sourceRoot":""}},{"offset":{"line":5,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js new file mode 100644 index 00000000000..df18fb27f3f --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Core"],function(i){"use strict";sap.ui.getCore().initLibrary({name:"library.i",version:"1.0.0",dependencies:["sap.ui.core"],types:["library.i.ButtonType","library.i.DialogType"],interfaces:["library.i.IContent"],controls:["library.i.Button","library.i.CheckBox","library.i.Dialog","library.i.Input","library.i.Label","library.i.Link","library.i.Menu","library.i.Text"],elements:["library.i.MenuItem"]});return thisLib}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js.map new file mode 100644 index 00000000000..a6926dc0c0d --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Core","getCore","initLibrary","name","version","dependencies","types","interfaces","controls","elements","thisLib"],"sources":["library-dbg.js"],"mappings":";;;AAGAA,IAAIC,GAAGC,OAAO,CACb,oBACE,SAASC,GAEX,aAEAH,IAAIC,GAAGG,UAAUC,YAAY,CAC5BC,KAAO,YACPC,QAAS,QACTC,aAAe,CAAC,eAChBC,MAAO,CACN,uBACA,wBAEDC,WAAY,CACX,sBAEDC,SAAU,CACT,mBACA,qBACA,mBACA,kBACA,kBACA,iBACA,iBACA,kBAEDC,SAAU,CACT,wBAIF,OAAOC,OAER","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/manifest.json b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/manifest.json new file mode 100644 index 00000000000..0179cfc90e6 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h-build-manifest/resources/library/i/manifest.json @@ -0,0 +1,91 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.i", + "type": "library", + "embeds": [], + "i18n": { + "bundleUrl": "i18nf/custommessagebundle.properties", + "supportedLocales": [ + "" + ], + "fallbackLocale": "" + }, + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "ach": "NOT-ME", + "resources": "resources.json", + "offline": false, + "openSourceComponents": [ + { + "name": "D3.js", + "packagedWithMySelf": false + }, + { + "name": "Hammer.js", + "packagedWithMySelf": true, + "version": "2.0.4" + } + ] + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + }, + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": { + "sap.ui.core": { + "minVersion": "1.0.0" + }, + "library.d": { + "lazy": true + } + } + }, + "contentDensities": { + "cozy": true, + "compact": false + }, + "library": { + "i18n": false, + "content": { + "controls": [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + "elements": [ + "library.i.MenuItem" + ], + "types": [ + "library.i.ButtonType", + "library.i.DialogType" + ], + "interfaces": [ + "library.i.IContent" + ] + } + } + }, + "sap.fiori": { + "registrationIds": [ + "F0042" + ], + "archeType": "fancycomponent" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/.library b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/.library new file mode 100644 index 00000000000..acfb88eed61 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/.library @@ -0,0 +1,44 @@ + + + + library.i + SAP SE + Some fancy copyright + 1.0.0 + + {{title}} + {{description}} + + + + sap.ui.core + + + library.d + true + + + + + + i18nf/custommessagebundle.properties + false + + sap_belize + sap_belize_plus + sap_belize_hcw + sap_belize_hcb + + + + + F0042 + fancycomponent + + + + NOT-ME + + + + diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js new file mode 100644 index 00000000000..bf457f50e5a --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/i/customLibraryHBundle-dbg.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' File '); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(' Library '); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(' Not including '); +//# sourceMappingURL=customLibraryHBundle-dbg.js.map diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js.map new file mode 100644 index 00000000000..8f3910f6f90 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customLibraryHBundle-dbg.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customLibraryHBundle-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"names":[],"sources":["file-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":9,"column":0},"map":{"version":3,"names":[],"sources":["library-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":15,"column":0},"map":{"version":3,"names":[],"sources":["some-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}},{"offset":{"line":23,"column":0},"map":{"version":3,"names":[],"sources":["not-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":"../h"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js new file mode 100644 index 00000000000..39fabf84473 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js @@ -0,0 +1,28 @@ +//@ui5-bundle library/i/customLibraryHBundle.js +sap.ui.require.preload({ + "library/h/file.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" File "); +}, + "library/h/library.js":function(){ +/*! + * Some fancy copyright + */ +console.log(" Library "); +}, + "library/h/some.js":function(){ +/*! + * Some fancy copyright + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(" Some "); +} +}); +//@ui5-bundle-raw-include library/h/not.js +/*! + * Some fancy copyright + */ +console.log(" Not including "); +//# sourceMappingURL=customLibraryHBundle.js.map diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js.map new file mode 100644 index 00000000000..c726fa3827d --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/customLibraryHBundle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"customLibraryHBundle.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["customLibraryHBundle.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"file.js","names":["console","log"],"sources":["file-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":9,"column":0},"map":{"version":3,"file":"library.js","names":["console","log"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":15,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;;AAIAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}},{"offset":{"line":23,"column":0},"map":{"version":3,"file":"not.js","names":["console","log"],"sources":["not-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":"../h"}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/i18nf/custommessagebundle.properties b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/i18nf/custommessagebundle.properties new file mode 100644 index 00000000000..dbb825dabd8 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/i18nf/custommessagebundle.properties @@ -0,0 +1 @@ +mytext=Hello \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library-dbg.js b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library-dbg.js new file mode 100644 index 00000000000..48a19acb752 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library-dbg.js @@ -0,0 +1,38 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], function(Core) { + + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.i", + version: "1.0.0", + dependencies : ["sap.ui.core"], + types: [ + "library.i.ButtonType", + "library.i.DialogType", + ], + interfaces: [ + "library.i.IContent", + ], + controls: [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + elements: [ + "library.i.MenuItem" + ], + }); + + return thisLib; + +}); diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js new file mode 100644 index 00000000000..df18fb27f3f --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Core"],function(i){"use strict";sap.ui.getCore().initLibrary({name:"library.i",version:"1.0.0",dependencies:["sap.ui.core"],types:["library.i.ButtonType","library.i.DialogType"],interfaces:["library.i.IContent"],controls:["library.i.Button","library.i.CheckBox","library.i.Dialog","library.i.Input","library.i.Label","library.i.Link","library.i.Menu","library.i.Text"],elements:["library.i.MenuItem"]});return thisLib}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js.map b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js.map new file mode 100644 index 00000000000..a6926dc0c0d --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Core","getCore","initLibrary","name","version","dependencies","types","interfaces","controls","elements","thisLib"],"sources":["library-dbg.js"],"mappings":";;;AAGAA,IAAIC,GAAGC,OAAO,CACb,oBACE,SAASC,GAEX,aAEAH,IAAIC,GAAGG,UAAUC,YAAY,CAC5BC,KAAO,YACPC,QAAS,QACTC,aAAe,CAAC,eAChBC,MAAO,CACN,uBACA,wBAEDC,WAAY,CACX,sBAEDC,SAAU,CACT,mBACA,qBACA,mBACA,kBACA,kBACA,iBACA,iBACA,kBAEDC,SAAU,CACT,wBAIF,OAAOC,OAER","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/manifest.json b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/manifest.json new file mode 100644 index 00000000000..0179cfc90e6 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/bundle-library.h/resources/library/i/manifest.json @@ -0,0 +1,91 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.i", + "type": "library", + "embeds": [], + "i18n": { + "bundleUrl": "i18nf/custommessagebundle.properties", + "supportedLocales": [ + "" + ], + "fallbackLocale": "" + }, + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "ach": "NOT-ME", + "resources": "resources.json", + "offline": false, + "openSourceComponents": [ + { + "name": "D3.js", + "packagedWithMySelf": false + }, + { + "name": "Hammer.js", + "packagedWithMySelf": true, + "version": "2.0.4" + } + ] + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + }, + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": { + "sap.ui.core": { + "minVersion": "1.0.0" + }, + "library.d": { + "lazy": true + } + } + }, + "contentDensities": { + "cozy": true, + "compact": false + }, + "library": { + "i18n": false, + "content": { + "controls": [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + "elements": [ + "library.i.MenuItem" + ], + "types": [ + "library.i.ButtonType", + "library.i.DialogType" + ], + "interfaces": [ + "library.i.IContent" + ] + } + } + }, + "sap.fiori": { + "registrationIds": [ + "F0042" + ], + "archeType": "fancycomponent" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/dest/resources/library/i/.library b/packages/builder/test/expected/build/library.i/dest/resources/library/i/.library new file mode 100644 index 00000000000..acfb88eed61 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/dest/resources/library/i/.library @@ -0,0 +1,44 @@ + + + + library.i + SAP SE + Some fancy copyright + 1.0.0 + + {{title}} + {{description}} + + + + sap.ui.core + + + library.d + true + + + + + + i18nf/custommessagebundle.properties + false + + sap_belize + sap_belize_plus + sap_belize_hcw + sap_belize_hcb + + + + + F0042 + fancycomponent + + + + NOT-ME + + + + diff --git a/packages/builder/test/expected/build/library.i/dest/resources/library/i/i18nf/custommessagebundle.properties b/packages/builder/test/expected/build/library.i/dest/resources/library/i/i18nf/custommessagebundle.properties new file mode 100644 index 00000000000..dbb825dabd8 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/dest/resources/library/i/i18nf/custommessagebundle.properties @@ -0,0 +1 @@ +mytext=Hello \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.i/dest/resources/library/i/library.js b/packages/builder/test/expected/build/library.i/dest/resources/library/i/library.js new file mode 100644 index 00000000000..48a19acb752 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/dest/resources/library/i/library.js @@ -0,0 +1,38 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], function(Core) { + + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.i", + version: "1.0.0", + dependencies : ["sap.ui.core"], + types: [ + "library.i.ButtonType", + "library.i.DialogType", + ], + interfaces: [ + "library.i.IContent", + ], + controls: [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + elements: [ + "library.i.MenuItem" + ], + }); + + return thisLib; + +}); diff --git a/packages/builder/test/expected/build/library.i/dest/resources/library/i/manifest.json b/packages/builder/test/expected/build/library.i/dest/resources/library/i/manifest.json new file mode 100644 index 00000000000..7a3ad6ed789 --- /dev/null +++ b/packages/builder/test/expected/build/library.i/dest/resources/library/i/manifest.json @@ -0,0 +1,92 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.i", + "type": "library", + "embeds": [], + "i18n": { + "bundleUrl": "i18nf/custommessagebundle.properties", + "supportedLocales": [ + "" + ], + "fallbackLocale": "" + }, + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "ach": "NOT-ME", + "resources": "resources.json", + "offline": false, + "openSourceComponents": [ + { + "name": "D3.js", + "packagedWithMySelf": false + }, + { + "name": "Hammer.js", + "packagedWithMySelf": true, + "version": "2.0.4" + } + ] + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + }, + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": { + "sap.ui.core": { + "minVersion": "1.0.0" + }, + "library.d": { + "minVersion": "1.0.0", + "lazy": true + } + } + }, + "contentDensities": { + "cozy": true, + "compact": false + }, + "library": { + "i18n": false, + "content": { + "controls": [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + "elements": [ + "library.i.MenuItem" + ], + "types": [ + "library.i.ButtonType", + "library.i.DialogType" + ], + "interfaces": [ + "library.i.IContent" + ] + } + } + }, + "sap.fiori": { + "registrationIds": [ + "F0042" + ], + "archeType": "fancycomponent" + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/.library b/packages/builder/test/expected/build/library.j/dest/resources/library/j/.library new file mode 100644 index 00000000000..1919da37e58 --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/.library @@ -0,0 +1,11 @@ + + + + library.j + SAP SE + Some fancy copyright + ${version} + + Library J + + diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/Foo.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/Foo.js new file mode 100644 index 00000000000..1bec63ae1cb --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/Foo.js @@ -0,0 +1,31 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunction + * - ChainExpression + * - ClassDeclaration + */ +(sap?.ui).define([`Bar`], (Bar) => { + /** + * @class + * My super documentation of this class + * + * @extends library.j.Bar + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.Foo + */ + class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + } + + return Foo; +}); diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/SubControl.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/SubControl.js new file mode 100644 index 00000000000..283fcaf16c7 --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/SubControl.js @@ -0,0 +1,45 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunctionExpression + */ + window.someRandomModule || + sap.ui.define( + ["sap/ui/core/Control"], + /** + * Constructor for a new library.j.SubControl. + * + * @param {string} [sId] ID for the new control, generated automatically if no ID is given + * @param {object} [mSettings] Initial settings for the new control + * + * @class + * + * @author SAP SE + * @version ${version} + * + * @constructor + * @extends sap.ui.core.Control + * @public + * @since 1.22 + * @alias library.j.SubControl + */ + (Control) => + Control.extend(`library.j.SubControl`, { + metadata: { + properties: { + /** + * MyProp property + * @since 1.46 + */ + MyProp: { + type: "boolean", + group: `Misc`, + defaultValue: false, + }, + }, + }, + }) + ); diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/ValidPropertyDefaultValue.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/ValidPropertyDefaultValue.js new file mode 100644 index 00000000000..61dbf44dbd5 --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/ValidPropertyDefaultValue.js @@ -0,0 +1,184 @@ +/*! + * ${copyright} + */ +sap.ui.define( + [ + "sap/ui/core/Control", + "./library", + "./core/library", + "sap/external/thirdparty/library", + "sap/external2/thirdparty/library", + "sap/external3/thirdparty/library", + ], + ( + Control, + { MyValidEnum, ThisIsEnumToo: RenamedEnum }, + coreLibrary, + [ {arrPattern}, {arrWith: {deep: arrPatternDeepDestruct}}], + { objPattern: {deeply: {destructured: objPatternDeepDestruct}, objPattern1Lvl} }, + libraryExt + ) => { + const { AnotherValidEnum } = coreLibrary; + const { Buzz } = AnotherValidEnum; + const { AnotherValidEnum: {Buzz: BuzzRenamed} } = coreLibrary; + const { AnotherValidEnum: AnotherRenamedEnum } = coreLibrary; + const { H1 } = sap.ui.core.TitleLevel; + const { Value2: RenamedValue2 } = RenamedEnum; + const [ {arrPatternVarDef}, {nested: {arrPatternVarDef: arrPatternVarDefNestedAndRenamed}} ] = libraryExt; + + /** + * @class + * My super documentation of this class + * + * @extends sap.ui.core.Control + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.ValidPropertyDefaultValue + * @ui5-metamodel text + */ + var ValidPropertyDefaultValue = Control.extend( + "library.j.ValidPropertyDefaultValue", + { + metadata: { + properties: { + /** + * validPropertyDefaultValueEnumSimpleDestructuring + */ + validPropertyDefaultValueEnumSimpleDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: AnotherValidEnum.Buzz + }, + + /** + * validPropertyDefaultValueEnumChainedDestructuring + */ + validPropertyDefaultValueEnumChainedDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: Buzz + }, + + /** + * validPropertyDefaultValueEnumNestedDestructuring + */ + validPropertyDefaultValueEnumNestedDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: BuzzRenamed + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRename + */ + validPropertyDefaultValueEnumDestructuringWithRename: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: AnotherRenamedEnum.Fizz + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRenameInArguments + */ + validPropertyDefaultValueEnumDestructuringWithRenameInArguments: + { + type: "library.j.ThisIsEnumToo", + group: "Misc", + defaultValue: RenamedEnum.Value1 + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar + */ + validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar: + { + type: "library.j.ThisIsEnumToo", + group: "Misc", + defaultValue: RenamedValue2 + }, + + /** + * validPropertyDefaultValueEnumViaDestructuringInArrowFn + */ + validPropertyDefaultValueEnumViaDestructuringInArrowFn: + { + type: "library.j.MyValidEnum", + group: "Misc", + defaultValue: MyValidEnum.Foo + }, + + /** + * validPropertyDefaultValueEnumViaDestructuringGlobal + */ + validPropertyDefaultValueEnumViaDestructuringGlobal: { + type: "sap.ui.core.TitleLevel", + group: "Misc", + defaultValue: H1 + }, + + /** + * validPropertyDefaultValueArrPattern + */ + validPropertyDefaultValueArrPattern: { + type: "sap.external.thirdparty.0", + group: "Misc", + defaultValue: arrPattern + }, + + /** + * validPropertyDefaultValueArrPatternDeepDestruct + */ + validPropertyDefaultValueArrPatternDeepDestruct: { + type: "sap.external.thirdparty.1.arrWith", + group: "Misc", + defaultValue: arrPatternDeepDestruct + }, + + /** + * validPropertyDefaultValueArrPatternDeepDestruct + */ + validPropertyDefaultValueObjPatternDeepDestruct: { + type: "sap.external2.thirdparty.objPattern.deeply", + group: "Misc", + defaultValue: objPatternDeepDestruct + }, + + /** + * validPropertyDefaultValueObjPatternNested + */ + validPropertyDefaultValueObjPatternNested: { + type: "sap.external2.thirdparty.objPattern", + group: "Misc", + defaultValue: objPattern1Lvl + }, + + /** + * validPropertyDefaultValueArrPatternVarDef + */ + validPropertyDefaultValueArrPatternVarDef: { + type: "sap.external3.thirdparty.0", + group: "Misc", + defaultValue: arrPatternVarDef + }, + + /** + * validPropertyDefaultValueArrPatternVarDef + */ + validPropertyDefaultValueArrPatternVarDefNestedAndRenamed: { + type: "sap.external3.thirdparty.1.nested", + group: "Misc", + defaultValue: arrPatternVarDefNestedAndRenamed + } + }, + }, + renderer: function () {}, + } + ); + + return ValidPropertyDefaultValue; + }, + /* bExport= */ true +); diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/core/library.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/core/library.js new file mode 100644 index 00000000000..ba22deece27 --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/core/library.js @@ -0,0 +1,34 @@ +/*! + * ${copyright} + */ +sap.ui.define([], function () { + sap.ui.getCore().initLibrary({ + name: "library.j.core", + version: "${version}", + dependencies: ["sap.ui.core"], + designtime: "library/j/core/designtime/library.designtime", + types: ["library.j.core.AnotherValidEnum"], + }); + + /** + * AnotherValidEnum + * + * @enum {string} + * @public + * @ui5-metamodel This enumeration also will be described in the UI5 (legacy) designtime metamodel + */ + library.j.core.AnotherValidEnum = { + /** + * Fizz + * @public + */ + Fizz: "Fizz", + /** + * Buzz + * @public + */ + Buzz: "Buzz", + }; + + return library.j.core; +}); diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/library.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/library.js new file mode 100644 index 00000000000..8a5d5343e0c --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/library.js @@ -0,0 +1,53 @@ +/*! + * ${copyright} + */ +sap.ui.define([], function () { + sap.ui.getCore().initLibrary({ + name: "library.j", + version: "${version}", + dependencies: ["sap.ui.core"], + designtime: "library/j/designtime/library.designtime", + types: ["library.j.MyValidEnum"], + }); + + /** + * MyValidEnum + * + * @enum {string} + * @public + * @ui5-metamodel This enumeration also will be described in the UI5 (legacy) designtime metamodel + */ + library.j.MyValidEnum = { + /** + * Foo + * @public + */ + Foo: "Foo", + /** + * Bar + * @public + */ + Bar: "Bar", + }; + + /** + * ThisIsEnumToo + * + * @enum {string} + * @public + */ + library.j.ThisIsEnumToo = { + /** + * Value1 + * @public + */ + Value1: "Value1", + /** + * Value2 + * @public + */ + Value2: "Value2", + }; + + return library.j; +}); diff --git a/packages/builder/test/expected/build/library.j/dest/resources/library/j/someGenerator.js b/packages/builder/test/expected/build/library.j/dest/resources/library/j/someGenerator.js new file mode 100644 index 00000000000..3f059af2c6f --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/resources/library/j/someGenerator.js @@ -0,0 +1,12 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - Generators + * - YeldExpression + */ +sap.ui.define([], function* someGenerator(genVar) { + yield genVar++; +}); diff --git a/packages/builder/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json b/packages/builder/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json new file mode 100644 index 00000000000..015bd8ba780 --- /dev/null +++ b/packages/builder/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json @@ -0,0 +1,843 @@ +{ + "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0", + "version": "1.0.0", + "library": "library.j", + "symbols": [ + { + "kind": "enum", + "name": "library.j.core.AnotherValidEnum", + "basename": "AnotherValidEnum", + "resource": "library/j/core/library.js", + "module": "library/j/core/library", + "export": "AnotherValidEnum", + "static": true, + "visibility": "public", + "description": "AnotherValidEnum\n\nThis enum is part of the 'library/j/core/library' module export and must be accessed by the property 'AnotherValidEnum'.", + "ui5-metamodel": true, + "ui5-metadata": { + "stereotype": "enum" + }, + "properties": [ + { + "name": "Buzz", + "visibility": "public", + "static": true, + "type": "string", + "description": "Buzz" + }, + { + "name": "Fizz", + "visibility": "public", + "static": true, + "type": "string", + "description": "Fizz" + } + ] + }, + { + "kind": "class", + "name": "library.j.Foo", + "basename": "Foo", + "resource": "library/j/Foo.js", + "module": "library/j/Foo", + "export": "", + "static": true, + "visibility": "public", + "extends": "library.j.Bar", + "description": "My super documentation of this class", + "constructor": { + "visibility": "public" + } + }, + { + "kind": "enum", + "name": "library.j.MyValidEnum", + "basename": "MyValidEnum", + "resource": "library/j/library.js", + "module": "library/j/library", + "export": "MyValidEnum", + "static": true, + "visibility": "public", + "description": "MyValidEnum\n\nThis enum is part of the 'library/j/library' module export and must be accessed by the property 'MyValidEnum'.", + "ui5-metamodel": true, + "ui5-metadata": { + "stereotype": "enum" + }, + "properties": [ + { + "name": "Bar", + "visibility": "public", + "static": true, + "type": "string", + "description": "Bar" + }, + { + "name": "Foo", + "visibility": "public", + "static": true, + "type": "string", + "description": "Foo" + } + ] + }, + { + "kind": "class", + "name": "library.j.SubControl", + "basename": "SubControl", + "resource": "library/j/SubControl.js", + "module": "library/j/SubControl", + "export": "", + "static": true, + "visibility": "public", + "since": "1.22", + "extends": "sap.ui.core.Control", + "ui5-metadata": { + "stereotype": "control", + "properties": [ + { + "name": "MyProp", + "type": "boolean", + "defaultValue": false, + "group": "undefined", + "visibility": "public", + "since": "1.46", + "description": "MyProp property", + "methods": [ + "getMyProp", + "setMyProp" + ] + } + ] + }, + "constructor": { + "visibility": "public", + "parameters": [ + { + "name": "sId", + "type": "string", + "optional": true, + "description": "ID for the new control, generated automatically if no ID is given" + }, + { + "name": "mSettings", + "type": "object", + "optional": true, + "description": "Initial settings for the new control" + } + ], + "description": "Constructor for a new library.j.SubControl." + }, + "methods": [ + { + "name": "extend", + "visibility": "public", + "static": true, + "returnValue": { + "type": "function", + "description": "Created class / constructor function" + }, + "parameters": [ + { + "name": "sClassName", + "type": "string", + "optional": false, + "description": "Name of the class being created" + }, + { + "name": "oClassInfo", + "type": "object", + "optional": true, + "description": "Object literal with information about the class" + }, + { + "name": "FNMetaImpl", + "type": "function", + "optional": true, + "description": "Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class" + } + ], + "description": "Creates a new subclass of class library.j.SubControl with name sClassName and enriches it with the information contained in oClassInfo.\n\noClassInfo might contain the same kind of information as described in {@link sap.ui.core.Control.extend}." + }, + { + "name": "getMetadata", + "visibility": "public", + "static": true, + "returnValue": { + "type": "sap.ui.base.Metadata", + "description": "Metadata object describing this class" + }, + "description": "Returns a metadata object for class library.j.SubControl." + }, + { + "name": "getMyProp", + "visibility": "public", + "since": "1.46", + "returnValue": { + "type": "boolean", + "description": "Value of property MyProp" + }, + "description": "Gets current value of property {@link #getMyProp MyProp}.\n\nMyProp property\n\nDefault value is false." + }, + { + "name": "setMyProp", + "visibility": "public", + "since": "1.46", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "bMyProp", + "type": "boolean", + "optional": true, + "defaultValue": false, + "description": "New value for property MyProp" + } + ], + "description": "Sets a new value for property {@link #getMyProp MyProp}.\n\nMyProp property\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is false." + } + ] + }, + { + "kind": "enum", + "name": "library.j.ThisIsEnumToo", + "basename": "ThisIsEnumToo", + "resource": "library/j/library.js", + "module": "library/j/library", + "export": "ThisIsEnumToo", + "static": true, + "visibility": "public", + "description": "ThisIsEnumToo\n\nThis enum is part of the 'library/j/library' module export and must be accessed by the property 'ThisIsEnumToo'.", + "ui5-metadata": { + "stereotype": "enum" + }, + "properties": [ + { + "name": "Value1", + "visibility": "public", + "static": true, + "type": "string", + "description": "Value1" + }, + { + "name": "Value2", + "visibility": "public", + "static": true, + "type": "string", + "description": "Value2" + } + ] + }, + { + "kind": "class", + "name": "library.j.ValidPropertyDefaultValue", + "basename": "ValidPropertyDefaultValue", + "resource": "library/j/ValidPropertyDefaultValue.js", + "module": "library/j/ValidPropertyDefaultValue", + "export": "", + "static": true, + "visibility": "public", + "extends": "sap.ui.core.Control", + "description": "My super documentation of this class", + "ui5-metamodel": true, + "ui5-metadata": { + "stereotype": "control", + "properties": [ + { + "name": "validPropertyDefaultValueEnumSimpleDestructuring", + "type": "library.j.core.AnotherValidEnum", + "defaultValue": "Buzz", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumSimpleDestructuring", + "methods": [ + "getValidPropertyDefaultValueEnumSimpleDestructuring", + "setValidPropertyDefaultValueEnumSimpleDestructuring" + ] + }, + { + "name": "validPropertyDefaultValueEnumChainedDestructuring", + "type": "library.j.core.AnotherValidEnum", + "defaultValue": "Buzz", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumChainedDestructuring", + "methods": [ + "getValidPropertyDefaultValueEnumChainedDestructuring", + "setValidPropertyDefaultValueEnumChainedDestructuring" + ] + }, + { + "name": "validPropertyDefaultValueEnumNestedDestructuring", + "type": "library.j.core.AnotherValidEnum", + "defaultValue": "Buzz", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumNestedDestructuring", + "methods": [ + "getValidPropertyDefaultValueEnumNestedDestructuring", + "setValidPropertyDefaultValueEnumNestedDestructuring" + ] + }, + { + "name": "validPropertyDefaultValueEnumDestructuringWithRename", + "type": "library.j.core.AnotherValidEnum", + "defaultValue": "Fizz", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumDestructuringWithRename", + "methods": [ + "getValidPropertyDefaultValueEnumDestructuringWithRename", + "setValidPropertyDefaultValueEnumDestructuringWithRename" + ] + }, + { + "name": "validPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "type": "library.j.ThisIsEnumToo", + "defaultValue": "Value1", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "methods": [ + "getValidPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "setValidPropertyDefaultValueEnumDestructuringWithRenameInArguments" + ] + }, + { + "name": "validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "type": "library.j.ThisIsEnumToo", + "defaultValue": "Value2", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "methods": [ + "getValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "setValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar" + ] + }, + { + "name": "validPropertyDefaultValueEnumViaDestructuringInArrowFn", + "type": "library.j.MyValidEnum", + "defaultValue": "Foo", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumViaDestructuringInArrowFn", + "methods": [ + "getValidPropertyDefaultValueEnumViaDestructuringInArrowFn", + "setValidPropertyDefaultValueEnumViaDestructuringInArrowFn" + ] + }, + { + "name": "validPropertyDefaultValueEnumViaDestructuringGlobal", + "type": "sap.ui.core.TitleLevel", + "defaultValue": "H1", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueEnumViaDestructuringGlobal", + "methods": [ + "getValidPropertyDefaultValueEnumViaDestructuringGlobal", + "setValidPropertyDefaultValueEnumViaDestructuringGlobal" + ] + }, + { + "name": "validPropertyDefaultValueArrPattern", + "type": "sap.external.thirdparty.0", + "defaultValue": "arrPattern", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueArrPattern", + "methods": [ + "getValidPropertyDefaultValueArrPattern", + "setValidPropertyDefaultValueArrPattern" + ] + }, + { + "name": "validPropertyDefaultValueArrPatternDeepDestruct", + "type": "sap.external.thirdparty.1.arrWith", + "defaultValue": "deep", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueArrPatternDeepDestruct", + "methods": [ + "getValidPropertyDefaultValueArrPatternDeepDestruct", + "setValidPropertyDefaultValueArrPatternDeepDestruct" + ] + }, + { + "name": "validPropertyDefaultValueObjPatternDeepDestruct", + "type": "sap.external2.thirdparty.objPattern.deeply", + "defaultValue": "destructured", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueArrPatternDeepDestruct", + "methods": [ + "getValidPropertyDefaultValueObjPatternDeepDestruct", + "setValidPropertyDefaultValueObjPatternDeepDestruct" + ] + }, + { + "name": "validPropertyDefaultValueObjPatternNested", + "type": "sap.external2.thirdparty.objPattern", + "defaultValue": "objPattern1Lvl", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueObjPatternNested", + "methods": [ + "getValidPropertyDefaultValueObjPatternNested", + "setValidPropertyDefaultValueObjPatternNested" + ] + }, + { + "name": "validPropertyDefaultValueArrPatternVarDef", + "type": "sap.external3.thirdparty.0", + "defaultValue": "arrPatternVarDef", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueArrPatternVarDef", + "methods": [ + "getValidPropertyDefaultValueArrPatternVarDef", + "setValidPropertyDefaultValueArrPatternVarDef" + ] + }, + { + "name": "validPropertyDefaultValueArrPatternVarDefNestedAndRenamed", + "type": "sap.external3.thirdparty.1.nested", + "defaultValue": "arrPatternVarDef", + "group": "Misc", + "visibility": "public", + "description": "validPropertyDefaultValueArrPatternVarDef", + "methods": [ + "getValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed", + "setValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed" + ] + } + ] + }, + "constructor": { + "visibility": "public", + "description": "Accepts an object literal mSettings that defines initial property values, aggregated and associated objects as well as event handlers. See {@link sap.ui.base.ManagedObject#constructor} for a general description of the syntax of the settings object." + }, + "methods": [ + { + "name": "extend", + "visibility": "public", + "static": true, + "returnValue": { + "type": "function", + "description": "Created class / constructor function" + }, + "parameters": [ + { + "name": "sClassName", + "type": "string", + "optional": false, + "description": "Name of the class being created" + }, + { + "name": "oClassInfo", + "type": "object", + "optional": true, + "description": "Object literal with information about the class" + }, + { + "name": "FNMetaImpl", + "type": "function", + "optional": true, + "description": "Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class" + } + ], + "description": "Creates a new subclass of class library.j.ValidPropertyDefaultValue with name sClassName and enriches it with the information contained in oClassInfo.\n\noClassInfo might contain the same kind of information as described in {@link sap.ui.core.Control.extend}." + }, + { + "name": "getMetadata", + "visibility": "public", + "static": true, + "returnValue": { + "type": "sap.ui.base.Metadata", + "description": "Metadata object describing this class" + }, + "description": "Returns a metadata object for class library.j.ValidPropertyDefaultValue." + }, + { + "name": "getValidPropertyDefaultValueArrPattern", + "visibility": "public", + "returnValue": { + "type": "sap.external.thirdparty.0", + "description": "Value of property validPropertyDefaultValueArrPattern" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueArrPattern validPropertyDefaultValueArrPattern}.\n\nvalidPropertyDefaultValueArrPattern\n\nDefault value is arrPattern." + }, + { + "name": "getValidPropertyDefaultValueArrPatternDeepDestruct", + "visibility": "public", + "returnValue": { + "type": "sap.external.thirdparty.1.arrWith", + "description": "Value of property validPropertyDefaultValueArrPatternDeepDestruct" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueArrPatternDeepDestruct validPropertyDefaultValueArrPatternDeepDestruct}.\n\nvalidPropertyDefaultValueArrPatternDeepDestruct\n\nDefault value is deep." + }, + { + "name": "getValidPropertyDefaultValueArrPatternVarDef", + "visibility": "public", + "returnValue": { + "type": "sap.external3.thirdparty.0", + "description": "Value of property validPropertyDefaultValueArrPatternVarDef" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueArrPatternVarDef validPropertyDefaultValueArrPatternVarDef}.\n\nvalidPropertyDefaultValueArrPatternVarDef\n\nDefault value is arrPatternVarDef." + }, + { + "name": "getValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed", + "visibility": "public", + "returnValue": { + "type": "sap.external3.thirdparty.1.nested", + "description": "Value of property validPropertyDefaultValueArrPatternVarDefNestedAndRenamed" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed validPropertyDefaultValueArrPatternVarDefNestedAndRenamed}.\n\nvalidPropertyDefaultValueArrPatternVarDef\n\nDefault value is arrPatternVarDef." + }, + { + "name": "getValidPropertyDefaultValueEnumChainedDestructuring", + "visibility": "public", + "returnValue": { + "type": "library.j.core.AnotherValidEnum", + "description": "Value of property validPropertyDefaultValueEnumChainedDestructuring" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumChainedDestructuring validPropertyDefaultValueEnumChainedDestructuring}.\n\nvalidPropertyDefaultValueEnumChainedDestructuring\n\nDefault value is Buzz." + }, + { + "name": "getValidPropertyDefaultValueEnumDestructuringWithRename", + "visibility": "public", + "returnValue": { + "type": "library.j.core.AnotherValidEnum", + "description": "Value of property validPropertyDefaultValueEnumDestructuringWithRename" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumDestructuringWithRename validPropertyDefaultValueEnumDestructuringWithRename}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRename\n\nDefault value is Fizz." + }, + { + "name": "getValidPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "visibility": "public", + "returnValue": { + "type": "library.j.ThisIsEnumToo", + "description": "Value of property validPropertyDefaultValueEnumDestructuringWithRenameInArguments" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumDestructuringWithRenameInArguments validPropertyDefaultValueEnumDestructuringWithRenameInArguments}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRenameInArguments\n\nDefault value is Value1." + }, + { + "name": "getValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "visibility": "public", + "returnValue": { + "type": "library.j.ThisIsEnumToo", + "description": "Value of property validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar\n\nDefault value is Value2." + }, + { + "name": "getValidPropertyDefaultValueEnumNestedDestructuring", + "visibility": "public", + "returnValue": { + "type": "library.j.core.AnotherValidEnum", + "description": "Value of property validPropertyDefaultValueEnumNestedDestructuring" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumNestedDestructuring validPropertyDefaultValueEnumNestedDestructuring}.\n\nvalidPropertyDefaultValueEnumNestedDestructuring\n\nDefault value is Buzz." + }, + { + "name": "getValidPropertyDefaultValueEnumSimpleDestructuring", + "visibility": "public", + "returnValue": { + "type": "library.j.core.AnotherValidEnum", + "description": "Value of property validPropertyDefaultValueEnumSimpleDestructuring" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumSimpleDestructuring validPropertyDefaultValueEnumSimpleDestructuring}.\n\nvalidPropertyDefaultValueEnumSimpleDestructuring\n\nDefault value is Buzz." + }, + { + "name": "getValidPropertyDefaultValueEnumViaDestructuringGlobal", + "visibility": "public", + "returnValue": { + "type": "sap.ui.core.TitleLevel", + "description": "Value of property validPropertyDefaultValueEnumViaDestructuringGlobal" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumViaDestructuringGlobal validPropertyDefaultValueEnumViaDestructuringGlobal}.\n\nvalidPropertyDefaultValueEnumViaDestructuringGlobal\n\nDefault value is H1." + }, + { + "name": "getValidPropertyDefaultValueEnumViaDestructuringInArrowFn", + "visibility": "public", + "returnValue": { + "type": "library.j.MyValidEnum", + "description": "Value of property validPropertyDefaultValueEnumViaDestructuringInArrowFn" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueEnumViaDestructuringInArrowFn validPropertyDefaultValueEnumViaDestructuringInArrowFn}.\n\nvalidPropertyDefaultValueEnumViaDestructuringInArrowFn\n\nDefault value is Foo." + }, + { + "name": "getValidPropertyDefaultValueObjPatternDeepDestruct", + "visibility": "public", + "returnValue": { + "type": "sap.external2.thirdparty.objPattern.deeply", + "description": "Value of property validPropertyDefaultValueObjPatternDeepDestruct" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueObjPatternDeepDestruct validPropertyDefaultValueObjPatternDeepDestruct}.\n\nvalidPropertyDefaultValueArrPatternDeepDestruct\n\nDefault value is destructured." + }, + { + "name": "getValidPropertyDefaultValueObjPatternNested", + "visibility": "public", + "returnValue": { + "type": "sap.external2.thirdparty.objPattern", + "description": "Value of property validPropertyDefaultValueObjPatternNested" + }, + "description": "Gets current value of property {@link #getValidPropertyDefaultValueObjPatternNested validPropertyDefaultValueObjPatternNested}.\n\nvalidPropertyDefaultValueObjPatternNested\n\nDefault value is objPattern1Lvl." + }, + { + "name": "setValidPropertyDefaultValueArrPattern", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueArrPattern", + "type": "sap.external.thirdparty.0", + "optional": true, + "defaultValue": "arrPattern", + "description": "New value for property validPropertyDefaultValueArrPattern" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueArrPattern validPropertyDefaultValueArrPattern}.\n\nvalidPropertyDefaultValueArrPattern\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is arrPattern." + }, + { + "name": "setValidPropertyDefaultValueArrPatternDeepDestruct", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueArrPatternDeepDestruct", + "type": "sap.external.thirdparty.1.arrWith", + "optional": true, + "defaultValue": "deep", + "description": "New value for property validPropertyDefaultValueArrPatternDeepDestruct" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueArrPatternDeepDestruct validPropertyDefaultValueArrPatternDeepDestruct}.\n\nvalidPropertyDefaultValueArrPatternDeepDestruct\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is deep." + }, + { + "name": "setValidPropertyDefaultValueArrPatternVarDef", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueArrPatternVarDef", + "type": "sap.external3.thirdparty.0", + "optional": true, + "defaultValue": "arrPatternVarDef", + "description": "New value for property validPropertyDefaultValueArrPatternVarDef" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueArrPatternVarDef validPropertyDefaultValueArrPatternVarDef}.\n\nvalidPropertyDefaultValueArrPatternVarDef\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is arrPatternVarDef." + }, + { + "name": "setValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed", + "type": "sap.external3.thirdparty.1.nested", + "optional": true, + "defaultValue": "arrPatternVarDef", + "description": "New value for property validPropertyDefaultValueArrPatternVarDefNestedAndRenamed" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueArrPatternVarDefNestedAndRenamed validPropertyDefaultValueArrPatternVarDefNestedAndRenamed}.\n\nvalidPropertyDefaultValueArrPatternVarDef\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is arrPatternVarDef." + }, + { + "name": "setValidPropertyDefaultValueEnumChainedDestructuring", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumChainedDestructuring", + "type": "library.j.core.AnotherValidEnum", + "optional": true, + "defaultValue": "Buzz", + "description": "New value for property validPropertyDefaultValueEnumChainedDestructuring" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumChainedDestructuring validPropertyDefaultValueEnumChainedDestructuring}.\n\nvalidPropertyDefaultValueEnumChainedDestructuring\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Buzz." + }, + { + "name": "setValidPropertyDefaultValueEnumDestructuringWithRename", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumDestructuringWithRename", + "type": "library.j.core.AnotherValidEnum", + "optional": true, + "defaultValue": "Fizz", + "description": "New value for property validPropertyDefaultValueEnumDestructuringWithRename" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumDestructuringWithRename validPropertyDefaultValueEnumDestructuringWithRename}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRename\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Fizz." + }, + { + "name": "setValidPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumDestructuringWithRenameInArguments", + "type": "library.j.ThisIsEnumToo", + "optional": true, + "defaultValue": "Value1", + "description": "New value for property validPropertyDefaultValueEnumDestructuringWithRenameInArguments" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumDestructuringWithRenameInArguments validPropertyDefaultValueEnumDestructuringWithRenameInArguments}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRenameInArguments\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Value1." + }, + { + "name": "setValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar", + "type": "library.j.ThisIsEnumToo", + "optional": true, + "defaultValue": "Value2", + "description": "New value for property validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar}.\n\nvalidPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Value2." + }, + { + "name": "setValidPropertyDefaultValueEnumNestedDestructuring", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumNestedDestructuring", + "type": "library.j.core.AnotherValidEnum", + "optional": true, + "defaultValue": "Buzz", + "description": "New value for property validPropertyDefaultValueEnumNestedDestructuring" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumNestedDestructuring validPropertyDefaultValueEnumNestedDestructuring}.\n\nvalidPropertyDefaultValueEnumNestedDestructuring\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Buzz." + }, + { + "name": "setValidPropertyDefaultValueEnumSimpleDestructuring", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumSimpleDestructuring", + "type": "library.j.core.AnotherValidEnum", + "optional": true, + "defaultValue": "Buzz", + "description": "New value for property validPropertyDefaultValueEnumSimpleDestructuring" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumSimpleDestructuring validPropertyDefaultValueEnumSimpleDestructuring}.\n\nvalidPropertyDefaultValueEnumSimpleDestructuring\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Buzz." + }, + { + "name": "setValidPropertyDefaultValueEnumViaDestructuringGlobal", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumViaDestructuringGlobal", + "type": "sap.ui.core.TitleLevel", + "optional": true, + "defaultValue": "H1", + "description": "New value for property validPropertyDefaultValueEnumViaDestructuringGlobal" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumViaDestructuringGlobal validPropertyDefaultValueEnumViaDestructuringGlobal}.\n\nvalidPropertyDefaultValueEnumViaDestructuringGlobal\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is H1." + }, + { + "name": "setValidPropertyDefaultValueEnumViaDestructuringInArrowFn", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueEnumViaDestructuringInArrowFn", + "type": "library.j.MyValidEnum", + "optional": true, + "defaultValue": "Foo", + "description": "New value for property validPropertyDefaultValueEnumViaDestructuringInArrowFn" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueEnumViaDestructuringInArrowFn validPropertyDefaultValueEnumViaDestructuringInArrowFn}.\n\nvalidPropertyDefaultValueEnumViaDestructuringInArrowFn\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is Foo." + }, + { + "name": "setValidPropertyDefaultValueObjPatternDeepDestruct", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueObjPatternDeepDestruct", + "type": "sap.external2.thirdparty.objPattern.deeply", + "optional": true, + "defaultValue": "destructured", + "description": "New value for property validPropertyDefaultValueObjPatternDeepDestruct" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueObjPatternDeepDestruct validPropertyDefaultValueObjPatternDeepDestruct}.\n\nvalidPropertyDefaultValueArrPatternDeepDestruct\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is destructured." + }, + { + "name": "setValidPropertyDefaultValueObjPatternNested", + "visibility": "public", + "returnValue": { + "type": "this", + "description": "Reference to this in order to allow method chaining" + }, + "parameters": [ + { + "name": "sValidPropertyDefaultValueObjPatternNested", + "type": "sap.external2.thirdparty.objPattern", + "optional": true, + "defaultValue": "objPattern1Lvl", + "description": "New value for property validPropertyDefaultValueObjPatternNested" + } + ], + "description": "Sets a new value for property {@link #getValidPropertyDefaultValueObjPatternNested validPropertyDefaultValueObjPatternNested}.\n\nvalidPropertyDefaultValueObjPatternNested\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is objPattern1Lvl." + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.k/dest/resources/library/k/.library b/packages/builder/test/expected/build/library.k/dest/resources/library/k/.library new file mode 100644 index 00000000000..641376edc4e --- /dev/null +++ b/packages/builder/test/expected/build/library.k/dest/resources/library/k/.library @@ -0,0 +1,12 @@ + + + + library.k + SAP SE + Some fancy copyright + 1.0.0 + + {{title}} + {{description}} + + diff --git a/packages/builder/test/expected/build/library.k/dest/resources/library/k/manifest-bundle/manifest.json b/packages/builder/test/expected/build/library.k/dest/resources/library/k/manifest-bundle/manifest.json new file mode 100644 index 00000000000..1db4fc6344f --- /dev/null +++ b/packages/builder/test/expected/build/library.k/dest/resources/library/k/manifest-bundle/manifest.json @@ -0,0 +1,49 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.k", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": { + "bundleUrl": "messagebundle.properties", + "supportedLocales": [ + "", + "de", + "en" + ] + }, + "content": { + "controls": [ + "library.k.AnyControl" + ], + "elements": [ + "library.k.AnyElement" + ], + "types": [ + "library.k.AnyType" + ], + "interfaces": [ + "library.k.IAny" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/.library b/packages/builder/test/expected/build/library.l/dest/resources/library/l/.library new file mode 100644 index 00000000000..4d03f7f5e6e --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/.library @@ -0,0 +1,11 @@ + + + + library.l + SAP SE + Some fancy copyright + 1.0.0 + + Library L + + diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/some-dbg.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some-dbg.js new file mode 100644 index 00000000000..7f815bcdd61 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some-dbg.js @@ -0,0 +1,16 @@ +/*! + * Some fancy copyright + */ + +sap.ui.define([], + function() { + "use strict"; + + /** + * @alias library.l + * @namespace + * @public + */ + var SomeFunction = function() {}; + +}, /* bExport= */ true); diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js new file mode 100644 index 00000000000..bf4a8a469fb --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([],function(){"use strict";var i=function(){}},true); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js.map b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js.map new file mode 100644 index 00000000000..1fe5d1f95cf --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["sap","ui","define","SomeFunction"],"sources":["some-dbg.js"],"mappings":";;;AAIAA,IAAIC,GAAGC,OAAO,GACb,WACA,aAOA,IAAIC,EAAe,WAAY,CAEhC,EAAkB","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index-dbg.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index-dbg.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index-dbg.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js new file mode 100644 index 00000000000..9661d72076f --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js @@ -0,0 +1,2 @@ +function hello(l){console.log("hello "+l)}hello("world"); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js.map b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js.map new file mode 100644 index 00000000000..bd6f85e8c8a --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","names":["hello","name","console","log"],"sources":["index-dbg.js"],"mappings":"AACA,SAASA,MAAMC,GACdC,QAAQC,IAAI,SAAWF,EACxB,CACAD,MAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File0.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1-dbg.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1-dbg.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1-dbg.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js new file mode 100644 index 00000000000..7b3311e4ee4 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js @@ -0,0 +1,2 @@ +function hello(l){console.log("hello "+l)}hello("world"); +//# sourceMappingURL=File1.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js.map b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js.map new file mode 100644 index 00000000000..1b9082c01f3 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/subdir/thirdparty/File1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"File1.js","names":["hello","name","console","log"],"sources":["File1-dbg.js"],"mappings":"AACA,SAASA,MAAMC,GACdC,QAAQC,IAAI,SAAWF,EACxB,CACAD,MAAM","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File0.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File1.js b/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/expected/build/library.l/dest/resources/library/l/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/expected/build/library.n/dest/resources/library/n/.library b/packages/builder/test/expected/build/library.n/dest/resources/library/n/.library new file mode 100644 index 00000000000..b11839e0ea7 --- /dev/null +++ b/packages/builder/test/expected/build/library.n/dest/resources/library/n/.library @@ -0,0 +1,11 @@ + + + + library.n + SAP SE + ${copyright} + ${version} + + Library N + + diff --git a/packages/builder/test/expected/build/library.n/dest/resources/library/n/MyModuleRequiringGlobalScope.js b/packages/builder/test/expected/build/library.n/dest/resources/library/n/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/expected/build/library.n/dest/resources/library/n/MyModuleRequiringGlobalScope.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/expected/build/library.n/dest/resources/library/n/Some.js b/packages/builder/test/expected/build/library.n/dest/resources/library/n/Some.js new file mode 100644 index 00000000000..c99ebbe708a --- /dev/null +++ b/packages/builder/test/expected/build/library.n/dest/resources/library/n/Some.js @@ -0,0 +1,7 @@ +/*! + * ${copyright} + */ +sap.ui.define([], () => { + const some = {text: "Just a module."}; + return some; +}) diff --git a/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js b/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js new file mode 100644 index 00000000000..9819c16939d --- /dev/null +++ b/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle library/n/library-preload.js +/*! + * ${copyright} + */ +sap.ui.predefine("library/n/Some", [], () => { + const some = {text: "Just a module."}; + return some; +}) +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js.map b/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js.map new file mode 100644 index 00000000000..a26e4594282 --- /dev/null +++ b/packages/builder/test/expected/build/library.n/dest/resources/library/n/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Some.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.n/dest/test-resources/library/n/Test.html b/packages/builder/test/expected/build/library.n/dest/test-resources/library/n/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.n/legacy/resources/library/n/.library b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/.library new file mode 100644 index 00000000000..b11839e0ea7 --- /dev/null +++ b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/.library @@ -0,0 +1,11 @@ + + + + library.n + SAP SE + ${copyright} + ${version} + + Library N + + diff --git a/packages/builder/test/expected/build/library.n/legacy/resources/library/n/MyModuleRequiringGlobalScope.js b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/MyModuleRequiringGlobalScope.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/expected/build/library.n/legacy/resources/library/n/Some.js b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/Some.js new file mode 100644 index 00000000000..c99ebbe708a --- /dev/null +++ b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/Some.js @@ -0,0 +1,7 @@ +/*! + * ${copyright} + */ +sap.ui.define([], () => { + const some = {text: "Just a module."}; + return some; +}) diff --git a/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js new file mode 100644 index 00000000000..8b1b30fecf6 --- /dev/null +++ b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js @@ -0,0 +1,12 @@ +//@ui5-bundle library/n/library-preload.js +/*! + * ${copyright} + */ +sap.ui.predefine("library/n/Some", [], () => { + const some = {text: "Just a module."}; + return some; +}) +sap.ui.require.preload({ + "library/n/MyModuleRequiringGlobalScope.js":'const magic = {text: "It\'s magic!"};\n' +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js.map b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js.map new file mode 100644 index 00000000000..b86bda9bee7 --- /dev/null +++ b/packages/builder/test/expected/build/library.n/legacy/resources/library/n/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["Some.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":8,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.n/legacy/test-resources/library/n/Test.html b/packages/builder/test/expected/build/library.n/legacy/test-resources/library/n/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/.library b/packages/builder/test/expected/build/library.o/dest/resources/library/o/.library new file mode 100644 index 00000000000..c36ef6f95f6 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/.library @@ -0,0 +1,12 @@ + + + + library.o + SAP SE + ${copyright} + 1.0.0 + + {{title}} + {{description}} + + diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-dbg.js b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-dbg.js new file mode 100644 index 00000000000..d8fcee8a38c --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-dbg.js @@ -0,0 +1,17 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], (Core) => { + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.o", + version: "1.0.0", + dependencies : [] + }); + + return thisLib; + +}); diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js new file mode 100644 index 00000000000..13fbc0911e4 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js @@ -0,0 +1,9 @@ +//@ui5-bundle library/o/library-preload.js +/*! + * Some fancy copyright + */ +sap.ui.predefine("library/o/library", ["sap/ui/core/Core"],e=>{"use strict";sap.ui.getCore().initLibrary({name:"library.o",version:"1.0.0",dependencies:[]});return thisLib}); +sap.ui.require.preload({ + "library/o/manifest.json":'{"_version":"1.58.0","sap.app":{"id":"library.o","type":"library","embeds":[],"applicationVersion":{"version":"1.0.0"},"title":"{{title}}","description":"{{description}}","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":[]},"sap.ui5":{"dependencies":{"libs":{}},"library":{"i18n":{"bundleUrl":"messagebundle.properties","terminologies":{"sports":{"bundleUrl":"sports.properties","bundleUrlRelativeTo":"manifest","supportedLocales":["","de","en"]},"travel":{"bundleUrl":"travel.properties","bundleUrlRelativeTo":"manifest","supportedLocales":["","de","en"]}},"enhanceWith":[{"bundleUrl":"myfolder1/i18n.properties","bundleUrlRelativeTo":"manifest","terminologies":{"sports":{"bundleUrl":"myfolder1/soccer.properties","bundleUrlRelativeTo":"manifest","supportedLocales":["","de","en"]},"travel":{"bundleUrl":"myfolder1/vehicles.properties","bundleUrlRelativeTo":"manifest","supportedLocales":["","de","en"]}},"supportedLocales":["","en"]},{"bundleUrl":"myfolder2/i18n.properties","bundleUrlRelativeTo":"manifest","terminologies":{"travel":{"bundleUrl":"myfolder2/bicycles.properties","bundleUrlRelativeTo":"manifest","supportedLocales":["","de","en"]}},"supportedLocales":["","en"]}],"supportedLocales":["","de","en"]}}}}' +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js.map b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js.map new file mode 100644 index 00000000000..4315683645a --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":["sap","ui","define","Core","getCore","initLibrary","name","version","dependencies","thisLib"],"sources":["library-dbg.js"],"mappings":"AAAA;;;AAGAA,IAAIC,GAAGC,+BAAO,CACb,oBACGC,IACH,aAEAH,IAAIC,GAAGG,UAAUC,YAAY,CAC5BC,KAAO,YACPC,QAAS,QACTC,aAAe,KAGhB,OAAOC","ignoreList":[],"sourceRoot":""}},{"offset":{"line":5,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js new file mode 100644 index 00000000000..cfdec4396a5 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +sap.ui.define(["sap/ui/core/Core"],e=>{"use strict";sap.ui.getCore().initLibrary({name:"library.o",version:"1.0.0",dependencies:[]});return thisLib}); +//# sourceMappingURL=library.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js.map b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js.map new file mode 100644 index 00000000000..dca5636bbe2 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/library.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library.js","names":["sap","ui","define","Core","getCore","initLibrary","name","version","dependencies","thisLib"],"sources":["library-dbg.js"],"mappings":";;;AAGAA,IAAIC,GAAGC,OAAO,CACb,oBACGC,IACH,aAEAH,IAAIC,GAAGG,UAAUC,YAAY,CAC5BC,KAAO,YACPC,QAAS,QACTC,aAAe,KAGhB,OAAOC","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/manifest.json b/packages/builder/test/expected/build/library.o/dest/resources/library/o/manifest.json new file mode 100644 index 00000000000..a75c7c1f466 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/manifest.json @@ -0,0 +1,103 @@ +{ + "_version": "1.58.0", + "sap.app": { + "id": "library.o", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "libs": {} + }, + "library": { + "i18n": { + "bundleUrl": "messagebundle.properties", + "terminologies": { + "sports": { + "bundleUrl": "sports.properties", + "bundleUrlRelativeTo": "manifest", + "supportedLocales": [ + "", + "de", + "en" + ] + }, + "travel": { + "bundleUrl": "travel.properties", + "bundleUrlRelativeTo": "manifest", + "supportedLocales": [ + "", + "de", + "en" + ] + } + }, + "enhanceWith": [ + { + "bundleUrl": "myfolder1/i18n.properties", + "bundleUrlRelativeTo": "manifest", + "terminologies": { + "sports": { + "bundleUrl": "myfolder1/soccer.properties", + "bundleUrlRelativeTo": "manifest", + "supportedLocales": [ + "", + "de", + "en" + ] + }, + "travel": { + "bundleUrl": "myfolder1/vehicles.properties", + "bundleUrlRelativeTo": "manifest", + "supportedLocales": [ + "", + "de", + "en" + ] + } + }, + "supportedLocales": [ + "", + "en" + ] + }, + { + "bundleUrl": "myfolder2/i18n.properties", + "bundleUrlRelativeTo": "manifest", + "terminologies": { + "travel": { + "bundleUrl": "myfolder2/bicycles.properties", + "bundleUrlRelativeTo": "manifest", + "supportedLocales": [ + "", + "de", + "en" + ] + } + }, + "supportedLocales": [ + "", + "en" + ] + } + ], + "supportedLocales": [ + "", + "de", + "en" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_de.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_de.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_en.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/messagebundle_en.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n.properties new file mode 100644 index 00000000000..7a2a6e8bb55 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n.properties @@ -0,0 +1 @@ +title=my company 1 title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n_en.properties new file mode 100644 index 00000000000..7a2a6e8bb55 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/i18n_en.properties @@ -0,0 +1 @@ +title=my company 1 title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer.properties new file mode 100644 index 00000000000..5ca5f663bcc --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer.properties @@ -0,0 +1 @@ +title=my soccer title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_de.properties new file mode 100644 index 00000000000..1c9b39ba50c --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_de.properties @@ -0,0 +1 @@ +title=Mein Fussball Titel \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_en.properties new file mode 100644 index 00000000000..5ca5f663bcc --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/soccer_en.properties @@ -0,0 +1 @@ +title=my soccer title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles.properties new file mode 100644 index 00000000000..2e42e0f6f03 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles.properties @@ -0,0 +1 @@ +title=my vehicle title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_de.properties new file mode 100644 index 00000000000..5cc4cab70cd --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_de.properties @@ -0,0 +1 @@ +title=Mein Fahrzeugstitel \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_en.properties new file mode 100644 index 00000000000..2e42e0f6f03 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder1/vehicles_en.properties @@ -0,0 +1 @@ +title=my vehicle title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles.properties new file mode 100644 index 00000000000..b288e4bfd1e --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles.properties @@ -0,0 +1 @@ +title=my bicycle title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_de.properties new file mode 100644 index 00000000000..f613ffa9d49 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_de.properties @@ -0,0 +1 @@ +title=Mein Fahrradtitel \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_en.properties new file mode 100644 index 00000000000..b288e4bfd1e --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/bicycles_en.properties @@ -0,0 +1 @@ +title=my bicycle title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n.properties new file mode 100644 index 00000000000..37564182772 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n.properties @@ -0,0 +1 @@ +title=my company 2 title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n_en.properties new file mode 100644 index 00000000000..37564182772 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/myfolder2/i18n_en.properties @@ -0,0 +1 @@ +title=my company 2 title \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports.properties new file mode 100644 index 00000000000..4b56228752f --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports.properties @@ -0,0 +1,2 @@ +title=a sports title +description=a sports description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_de.properties new file mode 100644 index 00000000000..c6ce6e0ffa2 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_de.properties @@ -0,0 +1,2 @@ +title=ein Sporttitel +description=eine Sportbeschreibung \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_en.properties new file mode 100644 index 00000000000..4b56228752f --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/sports_en.properties @@ -0,0 +1,2 @@ +title=a sports title +description=a sports description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel.properties new file mode 100644 index 00000000000..1e5ad188e03 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel.properties @@ -0,0 +1,2 @@ +title=a travel title +description=a travel description \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_de.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_de.properties new file mode 100644 index 00000000000..67689b27fc0 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_de.properties @@ -0,0 +1,2 @@ +title=ein Reisetitel +description=eine Reisebeschreibung \ No newline at end of file diff --git a/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_en.properties b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_en.properties new file mode 100644 index 00000000000..1e5ad188e03 --- /dev/null +++ b/packages/builder/test/expected/build/library.o/dest/resources/library/o/travel_en.properties @@ -0,0 +1,2 @@ +title=a travel title +description=a travel description \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/.library" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/.library" new file mode 100644 index 00000000000..593d069b84d --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/.library" @@ -0,0 +1,11 @@ + + + + library.ø + SAP SE + Some fancy copyright + 1.0.0 + + Library Ø + + diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js" new file mode 100644 index 00000000000..0a38dc7b093 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js" @@ -0,0 +1,11 @@ +//@ui5-bundle library/ø/library-preload.js +sap.ui.require.preload({ + "library/ø/manifest.json":'{"_version":"1.21.0","sap.app":{"id":"library.ø","type":"library","embeds":[],"applicationVersion":{"version":"1.0.0"},"title":"Library Ø","description":"Library Ø","resources":"resources.json","offline":true},"sap.ui":{"technology":"UI5","supportedThemes":["цветя"]},"sap.ui5":{"dependencies":{"minUI5Version":"1.0","libs":{}},"library":{"i18n":false}}}', + "library/ø/some.js":function(){ +/*! + * Some fancy copyright + */ +(function(){var o="World";console.log("Hello "+o)})(); +} +}); +//# sourceMappingURL=library-preload.js.map diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js.map" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js.map" new file mode 100644 index 00000000000..063a3cac79e --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.js.map" @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"file":"some.js","names":["someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;CAGA,WACC,IAAIA,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF,EACvB,EAHD","ignoreList":[],"sourceRoot":""}}]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js" new file mode 100644 index 00000000000..a4cbccba8c7 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js" @@ -0,0 +1,38 @@ +//@ui5-bundle library/ø/library-preload.support.js +/*! + * Some fancy copyright + */ +/** + * Defines support rules + */ + sap.ui.predefine("library/ø/rules/MyControl.support", ['sap/ui/support/library', 'sap/base/Log'], + function(SupportLib, Log) { + 'use strict'; + + //********************************************************** + // Rule Definitions + //********************************************************** + + var oRule = { + id: "oRule", + audiences: [Audiences.Application], + categories: [Categories.Usage], + enabled: true, + minversion: '1.71', + title: 'Title', + description: 'description', + resolution: 'resolution', + check: function(oIssueManager, oCoreFacade, oScope) { + oIssueManager.addIssue({ + severity: Severity.High, + details: 'Looking good today!' + }); + } + }; + + return [ + oRule + ]; + + }, true); +//# sourceMappingURL=library-preload.support.js.map diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js.map" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js.map" new file mode 100644 index 00000000000..0ef83d928f9 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/library-preload.support.js.map" @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.support.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["MyControl.support.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourceRoot":"rules"}}]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/manifest.json" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/manifest.json" new file mode 100644 index 00000000000..e242222ee0f --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/manifest.json" @@ -0,0 +1,30 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.ø", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library Ø", + "description": "Library Ø", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [ + "цветя" + ] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/rules/MyControl.support.js" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/rules/MyControl.support.js" new file mode 100644 index 00000000000..6f084d6ef6e --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/rules/MyControl.support.js" @@ -0,0 +1,36 @@ +/*! + * Some fancy copyright + */ +/** + * Defines support rules + */ + sap.ui.define(['sap/ui/support/library', 'sap/base/Log'], + function(SupportLib, Log) { + 'use strict'; + + //********************************************************** + // Rule Definitions + //********************************************************** + + var oRule = { + id: "oRule", + audiences: [Audiences.Application], + categories: [Categories.Usage], + enabled: true, + minversion: '1.71', + title: 'Title', + description: 'description', + resolution: 'resolution', + check: function(oIssueManager, oCoreFacade, oScope) { + oIssueManager.addIssue({ + severity: Severity.High, + details: 'Looking good today!' + }); + } + }; + + return [ + oRule + ]; + + }, true); diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some-dbg.js" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some-dbg.js" new file mode 100644 index 00000000000..1e8c6a2f952 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some-dbg.js" @@ -0,0 +1,7 @@ +/*! + * Some fancy copyright + */ +(function() { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +})(); diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js" new file mode 100644 index 00000000000..75aba1e2383 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js" @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +(function(){var o="World";console.log("Hello "+o)})(); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js.map" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js.map" new file mode 100644 index 00000000000..7d52c728fce --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/some.js.map" @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;CAGA,WACC,IAAIA,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF,EACvB,EAHD","ignoreList":[]} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-RTL.css" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-RTL.css" new file mode 100644 index 00000000000..9f2033d7ac7 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-RTL.css" @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-library\.ø{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-parameters.json" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-parameters.json" new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library-parameters.json" @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.css" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.css" new file mode 100644 index 00000000000..9f2033d7ac7 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.css" @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-library\.ø{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ "b/packages/builder/test/expected/build/library.\303\270/dest/resources/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git "a/packages/builder/test/expected/build/library.\303\270/dest/test-resources/library/\303\270/Test.html" "b/packages/builder/test/expected/build/library.\303\270/dest/test-resources/library/\303\270/Test.html" new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core-dbg.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core-dbg.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js new file mode 100644 index 00000000000..a72d4652560 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js @@ -0,0 +1,2 @@ + +//# sourceMappingURL=sap-ui-core.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js.map b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js.map new file mode 100644 index 00000000000..b663f06e68f --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap-ui-core.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-core.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global-dbg.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global-dbg.js new file mode 100644 index 00000000000..bc43cacae36 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('20220620-1630'); diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js new file mode 100644 index 00000000000..e0fe65e6f5b --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log("20220620-1630"); +//# sourceMappingURL=Global.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js.map b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js.map new file mode 100644 index 00000000000..cd9bc14ca67 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/Global.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Global.js","names":["console","log"],"sources":["Global-dbg.js"],"mappings":";;;AAIAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/.library b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/.library new file mode 100644 index 00000000000..f975d6a4e7e --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/.library @@ -0,0 +1,20 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + 1.0.0 + + Core + + + + + + + + + + diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core-dbg.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core-dbg.js new file mode 100644 index 00000000000..bc43cacae36 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('20220620-1630'); diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js new file mode 100644 index 00000000000..92d4a8ab045 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log("20220620-1630"); +//# sourceMappingURL=Core.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js.map b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js.map new file mode 100644 index 00000000000..e17213ac42f --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Core.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Core.js","names":["console","log"],"sources":["Core-dbg.js"],"mappings":";;;AAIAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon-dbg.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon-dbg.js new file mode 100644 index 00000000000..5147b81c463 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon-dbg.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('${buildtime}'); diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js new file mode 100644 index 00000000000..18fdec2b0a2 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +console.log("${buildtime}"); +//# sourceMappingURL=Icon.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js.map b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js.map new file mode 100644 index 00000000000..210ad686396 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core-buildtime/dest/resources/sap/ui/core/Icon.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Icon.js","names":["console","log"],"sources":["Icon-dbg.js"],"mappings":";;;AAIAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js new file mode 100644 index 00000000000..2e7bcbfcda9 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js @@ -0,0 +1,10 @@ +//@ui5-bundle sap-ui-core-dbg.js +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function () { + var thisIsTheUi5LoaderAutoconfig = true; + console.log(thisIsTheUi5LoaderAutoconfig); +})() +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +//# sourceMappingURL=sap-ui-core-dbg.js.map diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js.map new file mode 100644 index 00000000000..c14f83678da --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-core-dbg.js","sections":[{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-dbg.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js new file mode 100644 index 00000000000..85b8d131027 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js @@ -0,0 +1,10 @@ +//@ui5-bundle sap-ui-core-nojQuery-dbg.js +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function () { + var thisIsTheUi5LoaderAutoconfig = true; + console.log(thisIsTheUi5LoaderAutoconfig); +})() +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +//# sourceMappingURL=sap-ui-core-nojQuery-dbg.js.map diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js.map new file mode 100644 index 00000000000..f18cf9c928a --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery-dbg.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-core-nojQuery-dbg.js","sections":[{"offset":{"line":2,"column":0},"map":{"version":3,"names":[],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA;AACA;AACA;AACA;AACA","sourceRoot":""}},{"offset":{"line":6,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery-dbg.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery-dbg.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js new file mode 100644 index 00000000000..cb4bec46ae9 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js @@ -0,0 +1,17 @@ +//@ui5-bundle sap-ui-core-nojQuery.js +window["sap-ui-optimized"] = true; +try { +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function(){var o=true;console.log(o)})(); +sap.ui.require.preload({ + "sap/ui/core/Core.js":function(){ +(function(){var o=true;console.log(o)})(); +} +}); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +} catch(oError) { +if (oError.name != "Restart") { throw oError; } +} +//# sourceMappingURL=sap-ui-core-nojQuery.js.map diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js.map new file mode 100644 index 00000000000..a6f14f9f04a --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core-nojQuery.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-core-nojQuery.js","sections":[{"offset":{"line":4,"column":0},"map":{"version":3,"file":"ui5loader-autoconfig.js","names":["thisIsTheUi5LoaderAutoconfig","console","log"],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAA+B,KACnCC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":""}},{"offset":{"line":5,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"Core.js","names":["core","console","log"],"sources":["Core-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAAO,KACXC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":"sap/ui/core"}},{"offset":{"line":10,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":11,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery.js?bundle-code-2"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}},{"offset":{"line":13,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core-nojQuery.js?bundle-code-3"],"mappings":"AAAA;AACA;AACA","sourcesContent":["} catch(oError) {\nif (oError.name != \"Restart\") { throw oError; }\n}"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js new file mode 100644 index 00000000000..bf87d5315d8 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js @@ -0,0 +1,17 @@ +//@ui5-bundle sap-ui-core.js +window["sap-ui-optimized"] = true; +try { +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function(){var o=true;console.log(o)})(); +sap.ui.require.preload({ + "sap/ui/core/Core.js":function(){ +(function(){var o=true;console.log(o)})(); +} +}); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore?.().boot?.(); +} catch(oError) { +if (oError.name != "Restart") { throw oError; } +} +//# sourceMappingURL=sap-ui-core.js.map diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js.map new file mode 100644 index 00000000000..a19084e95f4 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap-ui-core.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sap-ui-core.js","sections":[{"offset":{"line":4,"column":0},"map":{"version":3,"file":"ui5loader-autoconfig.js","names":["thisIsTheUi5LoaderAutoconfig","console","log"],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAA+B,KACnCC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":""}},{"offset":{"line":5,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"Core.js","names":["core","console","log"],"sources":["Core-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAAO,KACXC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":"sap/ui/core"}},{"offset":{"line":10,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.requireSync(\"sap/ui/core/Core\");\n"],"sourceRoot":""}},{"offset":{"line":11,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core.js?bundle-code-2"],"mappings":"AAAA;AACA","sourcesContent":["// as this module contains the Core, we ensure that the Core has been booted\nsap.ui.getCore?.().boot?.();"],"sourceRoot":""}},{"offset":{"line":13,"column":0},"map":{"version":3,"names":[],"sources":["sap-ui-core.js?bundle-code-3"],"mappings":"AAAA;AACA;AACA","sourcesContent":["} catch(oError) {\nif (oError.name != \"Restart\") { throw oError; }\n}"],"sourceRoot":""}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/.library b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/.library new file mode 100644 index 00000000000..9089c4f9b38 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/.library @@ -0,0 +1,20 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + ${version} + + Core + + + + + + + + + + diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core-dbg.js new file mode 100644 index 00000000000..66bd2c43f22 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core-dbg.js @@ -0,0 +1,4 @@ +(function () { + var core = true; + console.log(core); +})() diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js new file mode 100644 index 00000000000..29a53cc7e5f --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js @@ -0,0 +1,2 @@ +(function(){var o=true;console.log(o)})(); +//# sourceMappingURL=Core.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js.map new file mode 100644 index 00000000000..e4ae647e2c5 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/Core.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Core.js","names":["core","console","log"],"sources":["Core-dbg.js"],"mappings":"CAAA,WACC,IAAIA,EAAO,KACXC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js new file mode 100644 index 00000000000..d368503ac78 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js @@ -0,0 +1,17 @@ +//@ui5-bundle sap/ui/core/library-preload.js +sap.ui.require.preload({ + "sap/ui/core/one.js":function(){ +function One(){return 1} +this.One=One; +}, + "sap/ui/core/some.js":function(){ +/*! + * ${copyright} + */ +console.log("HelloWorld"); +}, + "ui5loader.js":function(){ +(function(){var o=true;console.log(o)})(); +} +}); +//# sourceMappingURL=library-preload.js.map diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js.map new file mode 100644 index 00000000000..c4f8f6fdcc9 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/library-preload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"library-preload.js","sections":[{"offset":{"line":1,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-0"],"mappings":"AAAA;AACA","sourcesContent":["sap.ui.require.preload({\n"],"sourceRoot":""}},{"offset":{"line":3,"column":0},"map":{"version":3,"file":"one.js","names":["One"],"sources":["one-dbg.js"],"mappings":"AAAA,SAASA,MACR,OAAO,CACR","ignoreList":[],"sourceRoot":""}},{"offset":{"line":4,"column":0},"map":{"version":3,"names":[],"sources":["library-preload.js?bundle-code-1"],"mappings":"AAAA;AACA","sourcesContent":["this.One=One;\n"],"sourceRoot":""}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":"AAAA;;;AAGAA,QAAQC,IAAI","ignoreList":[],"sourceRoot":""}},{"offset":{"line":13,"column":0},"map":{"version":3,"file":"ui5loader.js","names":["thisIsTheUi5Loader","console","log"],"sources":["ui5loader-dbg.js"],"mappings":"AAAA,CAAA,WACC,IAAIA,EAAqB,KACzBC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[],"sourceRoot":"../../.."}}]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one-dbg.js new file mode 100644 index 00000000000..753bd052381 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one-dbg.js @@ -0,0 +1,3 @@ +function One(){ + return 1; +} diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js new file mode 100644 index 00000000000..7a53557c156 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js @@ -0,0 +1,2 @@ +function One(){return 1} +//# sourceMappingURL=one.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js.map new file mode 100644 index 00000000000..843614620eb --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/one.js.map @@ -0,0 +1 @@ +{"version":3,"file":"one.js","names":["One"],"sources":["one-dbg.js"],"mappings":"AAAA,SAASA,MACR,OAAO,CACR","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some-dbg.js new file mode 100644 index 00000000000..f39184874b5 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some-dbg.js @@ -0,0 +1,14 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); + +/* + * function add(c) - this is still not implemented. + * @private + * @param {Component} c + * @name add + * This comment should be removed in the preload bundle although it contains the magic + * sequence "opening parenthesis - letter c - closing parenthesis" which usually indicates + * a c o p y r i g h t comment. + */ diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js new file mode 100644 index 00000000000..66f0541d600 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +console.log("HelloWorld"); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js.map new file mode 100644 index 00000000000..7bfc96f5cd7 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/sap/ui/core/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["console","log"],"sources":["some-dbg.js"],"mappings":";;;AAGAA,QAAQC,IAAI","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig-dbg.js new file mode 100644 index 00000000000..783bce8ec31 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig-dbg.js @@ -0,0 +1,4 @@ +(function () { + var thisIsTheUi5LoaderAutoconfig = true; + console.log(thisIsTheUi5LoaderAutoconfig); +})() diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js new file mode 100644 index 00000000000..3cedd755e1b --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js @@ -0,0 +1,2 @@ +(function(){var o=true;console.log(o)})(); +//# sourceMappingURL=ui5loader-autoconfig.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js.map new file mode 100644 index 00000000000..aa71eace247 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-autoconfig.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ui5loader-autoconfig.js","names":["thisIsTheUi5LoaderAutoconfig","console","log"],"sources":["ui5loader-autoconfig-dbg.js"],"mappings":"CAAA,WACC,IAAIA,EAA+B,KACnCC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-dbg.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-dbg.js new file mode 100644 index 00000000000..5e44b1dd618 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader-dbg.js @@ -0,0 +1,4 @@ +(function () { + var thisIsTheUi5Loader = true; + console.log(thisIsTheUi5Loader); +})() diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js new file mode 100644 index 00000000000..863578d3de2 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js @@ -0,0 +1,2 @@ +(function(){var o=true;console.log(o)})(); +//# sourceMappingURL=ui5loader.js.map \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js.map b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js.map new file mode 100644 index 00000000000..d040f0d3b48 --- /dev/null +++ b/packages/builder/test/expected/build/sap.ui.core/preload/resources/ui5loader.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ui5loader.js","names":["thisIsTheUi5Loader","console","log"],"sources":["ui5loader-dbg.js"],"mappings":"CAAA,WACC,IAAIA,EAAqB,KACzBC,QAAQC,IAAIF,EACZ,EAHD","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/expected/build/sap.ui.core/preload/test-resources/sap/ui/core/Test.html b/packages/builder/test/expected/build/sap.ui.core/preload/test-resources/sap/ui/core/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/.ui5/build-manifest.json b/packages/builder/test/expected/build/theme.j/build-manifest/.ui5/build-manifest.json new file mode 100644 index 00000000000..e2beeb54d33 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/.ui5/build-manifest.json @@ -0,0 +1 @@ +// File content is not statically compared diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-RTL.css b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-parameters.json b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.css b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/build-manifest/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/.theming b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/.theming new file mode 100644 index 00000000000..4878adda883 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/.theming @@ -0,0 +1,6 @@ +{ + "sEntity": "Theme", + "sId": "somefancytheme", + "sVendor": "SAP", + "oExtends": "base" +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.css b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.css new file mode 100644 index 00000000000..6232d9e3503 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.css @@ -0,0 +1,3 @@ +:root{--someColor:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.less b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.less new file mode 100644 index 00000000000..91ce286944d --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.less @@ -0,0 +1,14 @@ +/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../base/css_variables.less"; + +/* START "css_variables.source.less" */ +@someColor: #000000; + +:root { +--someColor: @someColor; +} + +/* END "css_variables.source.less" */ + +@import "../../../../sap/ui/core/themes/somefancytheme/global.less"; diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.source.less b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.source.less new file mode 100644 index 00000000000..5a6ce08e9f9 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/css_variables.source.less @@ -0,0 +1,5 @@ +@someColor: #000000; + +:root { +--someColor: @someColor; +} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-RTL.css b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-parameters.json b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.css b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.less b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.less new file mode 100644 index 00000000000..24c4ca167cb --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.less @@ -0,0 +1,10 @@ +/* NOTE: This file was generated as an optimized version of "library.source.less" for the Theme Designer. */ + +@someColor: black; +/* START "Button.less" */ +.someClass { + color: @someColor +} + +/* END "Button.less" */ + diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css new file mode 100644 index 00000000000..7db08628997 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton.css b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton.css new file mode 100644 index 00000000000..7db08628997 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables-theme-designer-resources/resources/theme/j/themes/somefancytheme/library_skeleton.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css new file mode 100644 index 00000000000..6232d9e3503 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.css @@ -0,0 +1,3 @@ +:root{--someColor:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less new file mode 100644 index 00000000000..5a6ce08e9f9 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/css_variables.source.less @@ -0,0 +1,5 @@ +@someColor: #000000; + +:root { +--someColor: @someColor; +} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css new file mode 100644 index 00000000000..7db08628997 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton-RTL.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css new file mode 100644 index 00000000000..7db08628997 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-css-variables/resources/theme/j/themes/somefancytheme/library_skeleton.css @@ -0,0 +1 @@ +.someClass{color:var(--someColor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json new file mode 100644 index 00000000000..d4c81a92550 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json @@ -0,0 +1,37 @@ +{ + "_version": "1.1.0", + "resources": [ + { + "name": "Button.less", + "size": 34, + "designtime": true, + "theme": "somefancytheme" + }, + { + "name": "library-RTL.css", + "size": 162, + "theme": "somefancytheme" + }, + { + "name": "library-parameters.json", + "module": "theme/j/themes/somefancytheme/library-parameters.json", + "size": 20, + "theme": "somefancytheme" + }, + { + "name": "library.css", + "size": 162, + "theme": "somefancytheme" + }, + { + "name": "library.source.less", + "size": 42, + "designtime": true, + "theme": "somefancytheme" + }, + { + "name": "resources.json", + "size": 633 + } + ] +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-RTL.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json new file mode 100644 index 00000000000..a190cda03f6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library-parameters.json @@ -0,0 +1 @@ +{"someColor":"#000"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css new file mode 100644 index 00000000000..5009ca50e51 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.css @@ -0,0 +1,3 @@ +.someClass{color:#000} +/* Inline theming parameters */ +#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')} diff --git a/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/expected/build/theme.j/dest/resources/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theme b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theme new file mode 100644 index 00000000000..4b4b1cf98f2 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theme @@ -0,0 +1,9 @@ + + + + my_theme + me + Some fancy copyright + 1.0.0 + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theming b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theming new file mode 100644 index 00000000000..184073476c5 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/.theming @@ -0,0 +1,6 @@ +{ + "sEntity": "Theme", + "sId": "my_theme", + "sVendor": "SAP", + "oExtends": "base" +} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.css new file mode 100644 index 00000000000..48bb66a3e21 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.css @@ -0,0 +1,3 @@ +:root{--mycolor:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.less new file mode 100644 index 00000000000..41b1dc48ed6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.less @@ -0,0 +1,14 @@ +/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../base/css_variables.less"; + +/* START "css_variables.source.less" */ +@mycolor: #0000ff; + +:root { +--mycolor: @mycolor; +} + +/* END "css_variables.source.less" */ + +@import "../../../../../sap/ui/core/themes/my_theme/global.less"; diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.source.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.source.less new file mode 100644 index 00000000000..28ed8727ae6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/css_variables.source.less @@ -0,0 +1,5 @@ +@mycolor: #0000ff; + +:root { +--mycolor: @mycolor; +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-RTL.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-RTL.css new file mode 100644 index 00000000000..5eac03f06d0 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-RTL.css @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-parameters.json b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-parameters.json new file mode 100644 index 00000000000..a0c4913800e --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library-parameters.json @@ -0,0 +1 @@ +{"mycolor":"#00f"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.css new file mode 100644 index 00000000000..5eac03f06d0 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.css @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.less new file mode 100644 index 00000000000..f3fda6d3e8f --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.less @@ -0,0 +1,11 @@ +/* NOTE: This file was generated as an optimized version of "library.source.less" for the Theme Designer. */ + +/*! + * Some fancy copyright + */ + +@mycolor: blue; + +.sapUiBody { + background-color: @mycolor; +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.source.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.source.less new file mode 100644 index 00000000000..d864e666dd4 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library.source.less @@ -0,0 +1,9 @@ +/*! + * Some fancy copyright + */ + +@mycolor: blue; + +.sapUiBody { + background-color: @mycolor; +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css new file mode 100644 index 00000000000..654b3877e7c --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:var(--mycolor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton.css new file mode 100644 index 00000000000..654b3877e7c --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/resources/theme/library/e/themes/my_theme/library_skeleton.css @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:var(--mycolor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/test-resources/theme/library/e/Test.html b/packages/builder/test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources/test-resources/theme/library/e/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theme b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theme new file mode 100644 index 00000000000..4b4b1cf98f2 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theme @@ -0,0 +1,9 @@ + + + + my_theme + me + Some fancy copyright + 1.0.0 + + \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theming b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theming new file mode 100644 index 00000000000..83b6c785a87 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/.theming @@ -0,0 +1,27 @@ +{ + "sEntity": "Theme", + "sId": "sap_belize", + "oExtends": "base", + "sVendor": "SAP", + "aBundled": ["sap_belize_plus"], + "mCssScopes": { + "library": { + "sBaseFile": "library", + "sEmbeddingMethod": "APPEND", + "aScopes": [ + { + "sLabel": "Contrast", + "sSelector": "sapContrast", + "sEmbeddedFile": "sap_belize_plus.library", + "sEmbeddedCompareFile": "library", + "sThemeIdSuffix": "Contrast", + "sThemability": "PUBLIC", + "aThemabilityFilter": [ + "Color" + ], + "rExcludeSelector": "\\.sapContrastPlus\\W" + } + ] + } + } +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.css new file mode 100644 index 00000000000..48bb66a3e21 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.css @@ -0,0 +1,3 @@ +:root{--mycolor:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.source.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.source.less new file mode 100644 index 00000000000..28ed8727ae6 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/css_variables.source.less @@ -0,0 +1,5 @@ +@mycolor: #0000ff; + +:root { +--mycolor: @mycolor; +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-RTL.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-RTL.css new file mode 100644 index 00000000000..5eac03f06d0 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-RTL.css @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-parameters.json b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-parameters.json new file mode 100644 index 00000000000..a0c4913800e --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library-parameters.json @@ -0,0 +1 @@ +{"mycolor":"#00f"} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.css new file mode 100644 index 00000000000..5eac03f06d0 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.css @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:#00f} +/* Inline theming parameters */ +#sap-ui-theme-theme\.library\.e{background-image:url('data:text/plain;utf-8,%7B%22mycolor%22%3A%22%2300f%22%7D')} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.source.less b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.source.less new file mode 100644 index 00000000000..d864e666dd4 --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library.source.less @@ -0,0 +1,9 @@ +/*! + * Some fancy copyright + */ + +@mycolor: blue; + +.sapUiBody { + background-color: @mycolor; +} diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css new file mode 100644 index 00000000000..654b3877e7c --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton-RTL.css @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:var(--mycolor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton.css b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton.css new file mode 100644 index 00000000000..654b3877e7c --- /dev/null +++ b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/resources/theme/library/e/themes/my_theme/library_skeleton.css @@ -0,0 +1,3 @@ +/*! + * Some fancy copyright + */.sapUiBody{background-color:var(--mycolor)} \ No newline at end of file diff --git a/packages/builder/test/expected/build/theme.library.e/dest-css-variables/test-resources/theme/library/e/Test.html b/packages/builder/test/expected/build/theme.library.e/dest-css-variables/test-resources/theme/library/e/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/application.a/package.json b/packages/builder/test/fixtures/application.a/package.json new file mode 100644 index 00000000000..12d567553a0 --- /dev/null +++ b/packages/builder/test/fixtures/application.a/package.json @@ -0,0 +1,8 @@ +{ + "name": "application.a", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.a/ui5.yaml b/packages/builder/test/fixtures/application.a/ui5.yaml new file mode 100644 index 00000000000..e955414c058 --- /dev/null +++ b/packages/builder/test/fixtures/application.a/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.6" +type: application +metadata: + name: application.a diff --git a/packages/builder/test/fixtures/application.a/webapp/fileWithoutExtension b/packages/builder/test/fixtures/application.a/webapp/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/fixtures/application.a/webapp/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.a/webapp/index.html b/packages/builder/test/fixtures/application.a/webapp/index.html new file mode 100644 index 00000000000..1523b1dc3a9 --- /dev/null +++ b/packages/builder/test/fixtures/application.a/webapp/index.html @@ -0,0 +1,11 @@ + + + + Application A + + + + + + \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.a/webapp/manifest.json b/packages/builder/test/fixtures/application.a/webapp/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.a/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.a/webapp/test.js b/packages/builder/test/fixtures/application.a/webapp/test.js new file mode 100644 index 00000000000..cb459540525 --- /dev/null +++ b/packages/builder/test/fixtures/application.a/webapp/test.js @@ -0,0 +1,9 @@ +sap.ui.define([ + "library/d/some" +], function(someObject) { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/fixtures/application.b/package.json b/packages/builder/test/fixtures/application.b/package.json new file mode 100644 index 00000000000..0243e3a9001 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.b", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "main": "index.html", + "dependencies": { + "library.d": "file:../library.d", + "collection": "file:../collection" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.b/ui5.yaml b/packages/builder/test/fixtures/application.b/ui5.yaml new file mode 100644 index 00000000000..25326df459f --- /dev/null +++ b/packages/builder/test/fixtures/application.b/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.b diff --git a/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n.properties b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n.properties new file mode 100644 index 00000000000..d93c8a39c0e --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n.properties @@ -0,0 +1 @@ +title=embedded-i18n \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n_de.properties b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n_de.properties new file mode 100644 index 00000000000..e513333c842 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n/i18n_de.properties @@ -0,0 +1 @@ +title=embedded-i18n_de \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/embedded/i18n_fr.properties b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n_fr.properties new file mode 100644 index 00000000000..85e162740f9 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/embedded/i18n_fr.properties @@ -0,0 +1 @@ +title=embedded-i18n_fr-wrong \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/embedded/manifest.json b/packages/builder/test/fixtures/application.b/webapp/embedded/manifest.json new file mode 100644 index 00000000000..5ef0c362425 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/embedded/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1.embedded", + "type": "component", + "applicationVersion": { + "version": "1.2.2" + }, + "embeddedBy": "../", + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/i18n.properties b/packages/builder/test/fixtures/application.b/webapp/i18n.properties new file mode 100644 index 00000000000..d8d43be34d3 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/i18n/i18n.properties b/packages/builder/test/fixtures/application.b/webapp/i18n/i18n.properties new file mode 100644 index 00000000000..49f4756f982 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/i18n/i18n.properties @@ -0,0 +1,2 @@ +title=app-i18n +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/i18n/i18n_de.properties b/packages/builder/test/fixtures/application.b/webapp/i18n/i18n_de.properties new file mode 100644 index 00000000000..a81193360b3 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/i18n/i18n_de.properties @@ -0,0 +1,2 @@ +title=app-i18n_de +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/i18n/l10n.properties b/packages/builder/test/fixtures/application.b/webapp/i18n/l10n.properties new file mode 100644 index 00000000000..d8d43be34d3 --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/i18n/l10n.properties @@ -0,0 +1,2 @@ +title=app-i18n-wrong +fame=Strae \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.b/webapp/manifest.json b/packages/builder/test/fixtures/application.b/webapp/manifest.json new file mode 100644 index 00000000000..d33902df74e --- /dev/null +++ b/packages/builder/test/fixtures/application.b/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.c/package.json b/packages/builder/test/fixtures/application.c/package.json new file mode 100644 index 00000000000..1cd37e82d67 --- /dev/null +++ b/packages/builder/test/fixtures/application.c/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.c", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for dev dependencies. Optional dep gets resolved through root project", + "main": "index.html", + "dependencies": { + "library.e": "file:../library.e", + "library.d": "file:../library.d" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.c/src/manifest.json b/packages/builder/test/fixtures/application.c/src/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.c/src/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.c/ui5.yaml b/packages/builder/test/fixtures/application.c/ui5.yaml new file mode 100644 index 00000000000..3c1565db51f --- /dev/null +++ b/packages/builder/test/fixtures/application.c/ui5.yaml @@ -0,0 +1,9 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.c +resources: + configuration: + paths: + webapp: src diff --git a/packages/builder/test/fixtures/application.c2/package.json b/packages/builder/test/fixtures/application.c2/package.json new file mode 100644 index 00000000000..7af4231b145 --- /dev/null +++ b/packages/builder/test/fixtures/application.c2/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.c2", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for dev dependencies. Optional dep gets resolved through other project", + "main": "index.html", + "dependencies": { + "library.e": "file:../library.e", + "library.d-depender": "file:../library.d-depender" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.c2/src/manifest.json b/packages/builder/test/fixtures/application.c2/src/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.c2/src/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.c2/ui5.yaml b/packages/builder/test/fixtures/application.c2/ui5.yaml new file mode 100644 index 00000000000..132223e4280 --- /dev/null +++ b/packages/builder/test/fixtures/application.c2/ui5.yaml @@ -0,0 +1,9 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.c2 +resources: + configuration: + paths: + webapp: src diff --git a/packages/builder/test/fixtures/application.c3/package.json b/packages/builder/test/fixtures/application.c3/package.json new file mode 100644 index 00000000000..b51a0f27e23 --- /dev/null +++ b/packages/builder/test/fixtures/application.c3/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.c3", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for dev dependencies. Optional dep gets resolved through other project (but got hoisted)", + "main": "index.html", + "dependencies": { + "library.e": "file:../library.e", + "library.d-depender": "file:../library.d-depender" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.c3/src/manifest.json b/packages/builder/test/fixtures/application.c3/src/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.c3/src/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.c3/ui5.yaml b/packages/builder/test/fixtures/application.c3/ui5.yaml new file mode 100644 index 00000000000..b0fbde29c18 --- /dev/null +++ b/packages/builder/test/fixtures/application.c3/ui5.yaml @@ -0,0 +1,9 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.c3 +resources: + configuration: + paths: + webapp: src diff --git a/packages/builder/test/fixtures/application.d/package.json b/packages/builder/test/fixtures/application.d/package.json new file mode 100644 index 00000000000..e99f770892b --- /dev/null +++ b/packages/builder/test/fixtures/application.d/package.json @@ -0,0 +1,12 @@ +{ + "name": "application.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for dev dependencies", + "main": "index.html", + "dependencies": { + "library.e": "file:../library.e" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.d/ui5.yaml b/packages/builder/test/fixtures/application.d/ui5.yaml new file mode 100644 index 00000000000..60bbbc1c38b --- /dev/null +++ b/packages/builder/test/fixtures/application.d/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.d diff --git a/packages/builder/test/fixtures/application.d/webapp/manifest.json b/packages/builder/test/fixtures/application.d/webapp/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.d/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.e/package.json b/packages/builder/test/fixtures/application.e/package.json new file mode 100644 index 00000000000..50f927fcc52 --- /dev/null +++ b/packages/builder/test/fixtures/application.e/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for circular dependencies", + "main": "index.html", + "dependencies": { + "library.f": "file:../library.f", + "library.g": "file:../library.g" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.e/ui5.yaml b/packages/builder/test/fixtures/application.e/ui5.yaml new file mode 100644 index 00000000000..9828f57ae35 --- /dev/null +++ b/packages/builder/test/fixtures/application.e/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.e diff --git a/packages/builder/test/fixtures/application.e/webapp/manifest.json b/packages/builder/test/fixtures/application.e/webapp/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.e/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.f/package.json b/packages/builder/test/fixtures/application.f/package.json new file mode 100644 index 00000000000..68a71be8c58 --- /dev/null +++ b/packages/builder/test/fixtures/application.f/package.json @@ -0,0 +1,18 @@ +{ + "name": "application.f", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for ui5-dependencies configuration", + "main": "index.html", + "dependencies": { + "library.d": "file:../library.d", + "library.e": "file:../library.e" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "ui5": { + "dependencies": [ + "library.d" + ] + } +} diff --git a/packages/builder/test/fixtures/application.f/ui5.yaml b/packages/builder/test/fixtures/application.f/ui5.yaml new file mode 100644 index 00000000000..a6eda244408 --- /dev/null +++ b/packages/builder/test/fixtures/application.f/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: application +metadata: + name: application.f diff --git a/packages/builder/test/fixtures/application.f/webapp/manifest.json b/packages/builder/test/fixtures/application.f/webapp/manifest.json new file mode 100644 index 00000000000..781945df9dc --- /dev/null +++ b/packages/builder/test/fixtures/application.f/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.g/webapp/Component.js b/packages/builder/test/fixtures/application.g/webapp/Component.js new file mode 100644 index 00000000000..fd86d4bd8f7 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.g/webapp/manifest.json b/packages/builder/test/fixtures/application.g/webapp/manifest.json new file mode 100644 index 00000000000..56cc0ec3be9 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "${copyright}" +} diff --git a/packages/builder/test/fixtures/application.g/webapp/subcomponentA/Component.js b/packages/builder/test/fixtures/application.g/webapp/subcomponentA/Component.js new file mode 100644 index 00000000000..5f1b47a8a61 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/subcomponentA/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.g/webapp/subcomponentA/manifest.json b/packages/builder/test/fixtures/application.g/webapp/subcomponentA/manifest.json new file mode 100644 index 00000000000..ac1f84ae801 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.g/webapp/subcomponentB/Component.js b/packages/builder/test/fixtures/application.g/webapp/subcomponentB/Component.js new file mode 100644 index 00000000000..5fb07ed25c4 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/subcomponentB/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.g/webapp/subcomponentB/manifest.json b/packages/builder/test/fixtures/application.g/webapp/subcomponentB/manifest.json new file mode 100644 index 00000000000..84cd1802f51 --- /dev/null +++ b/packages/builder/test/fixtures/application.g/webapp/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.g.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.h/package.json b/packages/builder/test/fixtures/application.h/package.json new file mode 100644 index 00000000000..b49c3b7ed55 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.h", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "main": "index.html", + "dependencies": { + "library.d": "file:../library.d", + "collection": "file:../collection" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.h/pom.xml b/packages/builder/test/fixtures/application.h/pom.xml new file mode 100644 index 00000000000..478ebc85c8c --- /dev/null +++ b/packages/builder/test/fixtures/application.h/pom.xml @@ -0,0 +1,41 @@ + + + + + + + 4.0.0 + + + + + com.sap.test + application.h + 1.0.0 + war + + + + + application.h + Simple SAPUI5 based application + + + + + + + application.h + + + + + diff --git a/packages/builder/test/fixtures/application.h/webapp-project.artifactId/manifest.json b/packages/builder/test/fixtures/application.h/webapp-project.artifactId/manifest.json new file mode 100644 index 00000000000..7de6072ce82 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp-project.artifactId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${project.artifactId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.h/webapp-properties.appId/manifest.json b/packages/builder/test/fixtures/application.h/webapp-properties.appId/manifest.json new file mode 100644 index 00000000000..e1515df7025 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp-properties.appId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${appId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.h/webapp-properties.componentName/manifest.json b/packages/builder/test/fixtures/application.h/webapp-properties.componentName/manifest.json new file mode 100644 index 00000000000..7d63e359cdf --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp-properties.componentName/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${componentName}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.h/webapp/Component.js b/packages/builder/test/fixtures/application.h/webapp/Component.js new file mode 100644 index 00000000000..cb9bd406864 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.h.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/manifest.json b/packages/builder/test/fixtures/application.h/webapp/manifest.json new file mode 100644 index 00000000000..32b7e4a8458 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.h", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsA/section1.js b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section1.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsA/section2.js b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section2.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsA/section3.js b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section3.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsA/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsB/section1.js b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section1.js new file mode 100644 index 00000000000..ac4a8129651 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsB/section2.js b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section2.js new file mode 100644 index 00000000000..e009c828602 --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/packages/builder/test/fixtures/application.h/webapp/sectionsB/section3.js b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section3.js new file mode 100644 index 00000000000..5fd9349d49b --- /dev/null +++ b/packages/builder/test/fixtures/application.h/webapp/sectionsB/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/packages/builder/test/fixtures/application.i/webapp/Component.js b/packages/builder/test/fixtures/application.i/webapp/Component.js new file mode 100644 index 00000000000..f402cb1409d --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.i.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.i/webapp/changes/coding/MyExtension.js b/packages/builder/test/fixtures/application.i/webapp/changes/coding/MyExtension.js new file mode 100644 index 00000000000..dfb3da014ac --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/changes/coding/MyExtension.js @@ -0,0 +1,3 @@ +sap.ui.define([],function () { + return {}; +}); diff --git a/packages/builder/test/fixtures/application.i/webapp/changes/fragments/MyFragment.fragment.xml b/packages/builder/test/fixtures/application.i/webapp/changes/fragments/MyFragment.fragment.xml new file mode 100644 index 00000000000..39ce6859b45 --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/changes/fragments/MyFragment.fragment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.i/webapp/changes/id_123_addField.change b/packages/builder/test/fixtures/application.i/webapp/changes/id_123_addField.change new file mode 100644 index 00000000000..fc156d00c85 --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/changes/id_123_addField.change @@ -0,0 +1,20 @@ +{ + "fileName": "id_123_addField", + "fileType": "change", + "changeType": "hideControl", + "component": "application.i.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "CUSTOMER", + "texts": {}, + "namespace": "apps/application.i.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.i/webapp/changes/id_456_addField.change b/packages/builder/test/fixtures/application.i/webapp/changes/id_456_addField.change new file mode 100644 index 00000000000..0ca9acd3b66 --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/changes/id_456_addField.change @@ -0,0 +1,20 @@ +{ + "fileName": "id_456_addField", + "fileType": "change", + "changeType": "hideControl", + "component": "application.i.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.i.Component/changes", + "creation": "2023-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.i/webapp/manifest.json b/packages/builder/test/fixtures/application.i/webapp/manifest.json new file mode 100644 index 00000000000..d9da4a8b9a0 --- /dev/null +++ b/packages/builder/test/fixtures/application.i/webapp/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.i", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.72", + "libs": { + "sap.ui.layout": {}, + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/Component.js b/packages/builder/test/fixtures/application.j/webapp/Component.js new file mode 100644 index 00000000000..e40d21d9a27 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.j.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/coding/MyExtension.js b/packages/builder/test/fixtures/application.j/webapp/changes/coding/MyExtension.js new file mode 100644 index 00000000000..dfb3da014ac --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/coding/MyExtension.js @@ -0,0 +1,3 @@ +sap.ui.define([],function () { + return {}; +}); diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/fragments/MyFragment.fragment.xml b/packages/builder/test/fixtures/application.j/webapp/changes/fragments/MyFragment.fragment.xml new file mode 100644 index 00000000000..39ce6859b45 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/fragments/MyFragment.fragment.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_appDescriptor.change b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_appDescriptor.change new file mode 100644 index 00000000000..9c7f911b990 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_appDescriptor.change @@ -0,0 +1,21 @@ +{ + "fileName": "id_111_compVariants", + "fileType": "change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + }, + "appDescriptorChange": true +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_compVariants.variant b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_compVariants.variant new file mode 100644 index 00000000000..26431d338e5 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_compVariants.variant @@ -0,0 +1,21 @@ +{ + "fileName": "id_111_compVariants", + "fileType": "variant", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + }, + "appDescriptorChange": false +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant new file mode 100644 index 00000000000..01d2bfb4705 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant @@ -0,0 +1,20 @@ +{ + "fileName": "id_111_test", + "fileType": "ctrl_variant", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_change b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_change new file mode 100644 index 00000000000..8691c2e82aa --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_change @@ -0,0 +1,20 @@ +{ + "fileName": "id_111_test", + "fileType": "ctrl_variant_change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_management_change b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_management_change new file mode 100644 index 00000000000..1e1f585c848 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_test.ctrl_variant_management_change @@ -0,0 +1,20 @@ +{ + "fileName": "id_111_test", + "fileType": "ctrl_variant_management_change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_111_variantDependentControlChange.change b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_variantDependentControlChange.change new file mode 100644 index 00000000000..bd733b67e88 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_111_variantDependentControlChange.change @@ -0,0 +1,21 @@ +{ + "fileName": "id_111_variantDependentControlChange", + "fileType": "change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + }, + "variantReference": "someting here" +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_123_addField.change b/packages/builder/test/fixtures/application.j/webapp/changes/id_123_addField.change new file mode 100644 index 00000000000..5ab1f010869 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_123_addField.change @@ -0,0 +1,20 @@ +{ + "fileName": "id_123_addField", + "fileType": "change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "CUSTOMER", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2025-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/changes/id_456_addField.change b/packages/builder/test/fixtures/application.j/webapp/changes/id_456_addField.change new file mode 100644 index 00000000000..4d4c73b51b2 --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/changes/id_456_addField.change @@ -0,0 +1,20 @@ +{ + "fileName": "id_456_addField", + "fileType": "change", + "changeType": "hideControl", + "component": "application.j.Component", + "content": {}, + "selector": { + "id": "control1" + }, + "layer": "VENDOR", + "texts": {}, + "namespace": "apps/application.j.Component/changes", + "creation": "2023-10-30T13:52:40.4754350Z", + "originalLanguage": "", + "conditions": {}, + "support": { + "generator": "did it", + "user": "Max Mustermann" + } +} diff --git a/packages/builder/test/fixtures/application.j/webapp/fileWithoutExtension b/packages/builder/test/fixtures/application.j/webapp/fileWithoutExtension new file mode 100644 index 00000000000..1a39ca7a71f --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/fileWithoutExtension @@ -0,0 +1 @@ +A file without an extension \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.j/webapp/manifest.json b/packages/builder/test/fixtures/application.j/webapp/manifest.json new file mode 100644 index 00000000000..dd82248acfd --- /dev/null +++ b/packages/builder/test/fixtures/application.j/webapp/manifest.json @@ -0,0 +1,26 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.j", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.100.2", + "libs": { + "sap.ui.layout": {}, + "sap.ui.core": {}, + "sap.m": {}, + "sap.ui.fl": { + "lazy": true + } + } + } + } +} diff --git a/packages/builder/test/fixtures/application.k/package.json b/packages/builder/test/fixtures/application.k/package.json new file mode 100644 index 00000000000..970a84df3b3 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/package.json @@ -0,0 +1,13 @@ +{ + "name": "application.k", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "main": "index.html", + "dependencies": { + "library.d": "file:../library.d", + "collection": "file:../collection" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.k/webapp/Component.js b/packages/builder/test/fixtures/application.k/webapp/Component.js new file mode 100644 index 00000000000..b386a26a9c4 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.k/webapp/manifest.json b/packages/builder/test/fixtures/application.k/webapp/manifest.json new file mode 100644 index 00000000000..ffed9444e37 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/manifest.json @@ -0,0 +1,14 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "customCopyrightString": "${copyright}" +} diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentA/Component.js b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/Component.js new file mode 100644 index 00000000000..faed14a03eb --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentA.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentA/manifest.json b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/manifest.json new file mode 100644 index 00000000000..f6d29cb2dbf --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentA", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentA/thirdparty/lib.js b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/thirdparty/lib.js new file mode 100644 index 00000000000..cd35259d2b4 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentA/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentA/thirdparty/lib.js"); diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentB/Component.js b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/Component.js new file mode 100644 index 00000000000..b713ad894c4 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.k.subcomponentB.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentB/manifest.json b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/manifest.json new file mode 100644 index 00000000000..88f052556d0 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.k.subcomponentB", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.k/webapp/subcomponentB/thirdparty/lib.js b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/thirdparty/lib.js new file mode 100644 index 00000000000..6f4d127a4c3 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/subcomponentB/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("subcomponentB/thirdparty/lib.js"); diff --git a/packages/builder/test/fixtures/application.k/webapp/thirdparty/lib.js b/packages/builder/test/fixtures/application.k/webapp/thirdparty/lib.js new file mode 100644 index 00000000000..b52c6b26a70 --- /dev/null +++ b/packages/builder/test/fixtures/application.k/webapp/thirdparty/lib.js @@ -0,0 +1 @@ +console.log("thirdparty/lib.js"); diff --git a/packages/builder/test/fixtures/application.l/package.json b/packages/builder/test/fixtures/application.l/package.json new file mode 100644 index 00000000000..0ac094f9501 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/package.json @@ -0,0 +1,10 @@ +{ + "name": "application.l", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "main": "index.html", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.l/webapp/index.html b/packages/builder/test/fixtures/application.l/webapp/index.html new file mode 100644 index 00000000000..4c3be4c7cbd --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/index.html @@ -0,0 +1,11 @@ + + + + Application L + + + + + + diff --git a/packages/builder/test/fixtures/application.l/webapp/manifest.json b/packages/builder/test/fixtures/application.l/webapp/manifest.json new file mode 100644 index 00000000000..c6ed57a4fdd --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.l", + "type": "application", + "applicationVersion": { + "version": "${version}" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/packages/builder/test/fixtures/application.l/webapp/subdir/index.js b/packages/builder/test/fixtures/application.l/webapp/subdir/index.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/subdir/index.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File0.js b/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File1.js b/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/subdir/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/application.l/webapp/test.js b/packages/builder/test/fixtures/application.l/webapp/test.js new file mode 100644 index 00000000000..6d767ba0569 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/test.js @@ -0,0 +1,7 @@ +sap.ui.define([], function() { + function test(paramA) { + var variableA = paramA; + console.log(variableA); + } + test(); +}); diff --git a/packages/builder/test/fixtures/application.l/webapp/thirdparty/File0.js b/packages/builder/test/fixtures/application.l/webapp/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/application.l/webapp/thirdparty/File1.js b/packages/builder/test/fixtures/application.l/webapp/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/application.l/webapp/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/application.m/package.json b/packages/builder/test/fixtures/application.m/package.json new file mode 100644 index 00000000000..d70e686e855 --- /dev/null +++ b/packages/builder/test/fixtures/application.m/package.json @@ -0,0 +1,8 @@ +{ + "name": "application.m", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.m/ui5.yaml b/packages/builder/test/fixtures/application.m/ui5.yaml new file mode 100644 index 00000000000..8174c6fe31c --- /dev/null +++ b/packages/builder/test/fixtures/application.m/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.6" +type: application +metadata: + name: application.m diff --git a/packages/builder/test/fixtures/application.m/webapp/Component.js b/packages/builder/test/fixtures/application.m/webapp/Component.js new file mode 100644 index 00000000000..51a736bf6bc --- /dev/null +++ b/packages/builder/test/fixtures/application.m/webapp/Component.js @@ -0,0 +1,16 @@ +sap.ui.define(["sap/ui/core/UIComponent", "sap/ui/core/mvc/View"], (UIComponent, View) => { + "use strict"; + return UIComponent.extend("application.m.Component", { + metadata: { + manifest: "json", + interfaces: [ + "sap.ui.core.IAsyncContentCreation" + ] + }, + createContent() { + return View.create({ + viewName: "module:application/m/MyView" + }) + } + }); +}); diff --git a/packages/builder/test/fixtures/application.m/webapp/MyView.js b/packages/builder/test/fixtures/application.m/webapp/MyView.js new file mode 100644 index 00000000000..0f137931685 --- /dev/null +++ b/packages/builder/test/fixtures/application.m/webapp/MyView.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +sap.ui.define([ + "sap/ui/core/mvc/View", + "sap/m/Button" + ], (View, Button) => { + "use strict"; + return View.extend("application.m.MyView", { + async createContent() { + return new Button({ + id: this.createId("myButton"), + text: "My Button" + }); + } + }); + }); diff --git a/packages/builder/test/fixtures/application.m/webapp/index.html b/packages/builder/test/fixtures/application.m/webapp/index.html new file mode 100644 index 00000000000..c60ee64599c --- /dev/null +++ b/packages/builder/test/fixtures/application.m/webapp/index.html @@ -0,0 +1,24 @@ + + + + + + Application M + + + + + +
    + + diff --git a/packages/builder/test/fixtures/application.m/webapp/manifest.json b/packages/builder/test/fixtures/application.m/webapp/manifest.json new file mode 100644 index 00000000000..32aa4794772 --- /dev/null +++ b/packages/builder/test/fixtures/application.m/webapp/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "application.m", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + } + }, + "sap.ui5": { + "contentDensities": { + "compact": true, + "cozy": true + }, + "dependencies": { + "minUI5Version": "1.108.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/fixtures/application.n/package.json b/packages/builder/test/fixtures/application.n/package.json new file mode 100644 index 00000000000..75580d58288 --- /dev/null +++ b/packages/builder/test/fixtures/application.n/package.json @@ -0,0 +1,8 @@ +{ + "name": "application.n", + "version": "1.0.0", + "description": "Simple SAPUI5 based application - test for disabled string bundling", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.n/webapp/Component.js b/packages/builder/test/fixtures/application.n/webapp/Component.js new file mode 100644 index 00000000000..0e58f2fbed9 --- /dev/null +++ b/packages/builder/test/fixtures/application.n/webapp/Component.js @@ -0,0 +1,11 @@ +sap.ui.define(["sap/ui/core/UIComponent", "sap/m/Button", "application/n/MyModuleRequiringGlobalScope"], (UIComponent, Button) => { + "use strict"; + return UIComponent.extend("application.n.Component", { + metadata: { + manifest: "json" + }, + createContent() { + return new Button({text: magic.text}); + } + }); +}); diff --git a/packages/builder/test/fixtures/application.n/webapp/MyModuleRequiringGlobalScope.js b/packages/builder/test/fixtures/application.n/webapp/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/fixtures/application.n/webapp/MyModuleRequiringGlobalScope.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/fixtures/application.n/webapp/index.html b/packages/builder/test/fixtures/application.n/webapp/index.html new file mode 100644 index 00000000000..8798c3b8ddd --- /dev/null +++ b/packages/builder/test/fixtures/application.n/webapp/index.html @@ -0,0 +1,24 @@ + + + + + + Application N + + + + + +
    + + diff --git a/packages/builder/test/fixtures/application.n/webapp/manifest.json b/packages/builder/test/fixtures/application.n/webapp/manifest.json new file mode 100644 index 00000000000..1d7d445206e --- /dev/null +++ b/packages/builder/test/fixtures/application.n/webapp/manifest.json @@ -0,0 +1,23 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "application.n", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + } + }, + "sap.ui5": { + "contentDensities": { + "compact": true, + "cozy": true + }, + "dependencies": { + "minUI5Version": "1.108.0", + "libs": { + "sap.ui.core": {}, + "sap.m": {} + } + } + } +} diff --git a/packages/builder/test/fixtures/application.o/package.json b/packages/builder/test/fixtures/application.o/package.json new file mode 100644 index 00000000000..b4acde06e26 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/package.json @@ -0,0 +1,8 @@ +{ + "name": "application.o", + "version": "1.0.0", + "description": "Simple SAPUI5 based application", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/application.o/ui5.yaml b/packages/builder/test/fixtures/application.o/ui5.yaml new file mode 100644 index 00000000000..28318b58d4e --- /dev/null +++ b/packages/builder/test/fixtures/application.o/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "3.2" +type: application +metadata: + name: application.o diff --git a/packages/builder/test/fixtures/application.o/webapp/i18n/i18n.properties b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n.properties new file mode 100644 index 00000000000..4fa9396ef8a --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n.properties @@ -0,0 +1 @@ +welcome=Hello world \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en.properties b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en.properties new file mode 100644 index 00000000000..f03a373b0b8 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en.properties @@ -0,0 +1 @@ +welcome=Hello EN world \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US.properties b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US.properties new file mode 100644 index 00000000000..60d20479d61 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US.properties @@ -0,0 +1 @@ +welcome=Hello EN US world \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US_saptrc.properties b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US_saptrc.properties new file mode 100644 index 00000000000..88ea197de70 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/i18n/i18n_en_US_saptrc.properties @@ -0,0 +1 @@ +welcome=Hello EN US saptrc world \ No newline at end of file diff --git a/packages/builder/test/fixtures/application.o/webapp/index.html b/packages/builder/test/fixtures/application.o/webapp/index.html new file mode 100644 index 00000000000..991958fa5bf --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/index.html @@ -0,0 +1,11 @@ + + + + Application O + + + + + + diff --git a/packages/builder/test/fixtures/application.o/webapp/manifest.json b/packages/builder/test/fixtures/application.o/webapp/manifest.json new file mode 100644 index 00000000000..6f103ad8d08 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/manifest.json @@ -0,0 +1,27 @@ +{ + "_version": "1.22.0", + "sap.app": { + "id": "application.o", + "type": "application", + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}" + }, + "sap.ui5": { + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "application.o.i18n.i18n" + } + }, + "i18n-ui5": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleUrl": "ui5://application/o/i18n/i18n.properties" + } + } + } + } +} diff --git a/packages/builder/test/fixtures/application.o/webapp/test.js b/packages/builder/test/fixtures/application.o/webapp/test.js new file mode 100644 index 00000000000..399a1b6a147 --- /dev/null +++ b/packages/builder/test/fixtures/application.o/webapp/test.js @@ -0,0 +1,8 @@ +sap.ui.define([ +], () => { + test((paramA) => { + const variableA = paramA; + console.log(variableA); + }) + test(); +}); diff --git "a/packages/builder/test/fixtures/application.\303\270/package.json" "b/packages/builder/test/fixtures/application.\303\270/package.json" new file mode 100644 index 00000000000..0d624840932 --- /dev/null +++ "b/packages/builder/test/fixtures/application.\303\270/package.json" @@ -0,0 +1,10 @@ +{ + "name": "application.ø", + "version": "1.0.0", + "description": "SAPUI5 application for unicode testing", + "main": "index.html", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git "a/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/Component.js" "b/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/Component.js" new file mode 100644 index 00000000000..1036a390e61 --- /dev/null +++ "b/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/Component.js" @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.ø.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git "a/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/manifest.json" "b/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/manifest.json" new file mode 100644 index 00000000000..435adbfeb7a --- /dev/null +++ "b/packages/builder/test/fixtures/application.\303\270/w\303\252b\303\244pp/manifest.json" @@ -0,0 +1,21 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.ø", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.73.2", + "libs": { + "sap.ui.core": {} + } + } + } +} diff --git a/packages/builder/test/fixtures/collection/library.a/src/library/a/.library b/packages/builder/test/fixtures/collection/library.a/src/library/a/.library new file mode 100644 index 00000000000..ef0ea1065bc --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.a/src/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + Some fancy copyright ${currentYear} + ${version} + + Library A + + + + library.d + + + + diff --git a/packages/builder/test/fixtures/collection/library.a/src/library/a/themes/base/library.source.less b/packages/builder/test/fixtures/collection/library.a/src/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.a/src/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/builder/test/fixtures/collection/library.a/test/library/a/Test.html b/packages/builder/test/fixtures/collection/library.a/test/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/collection/library.b/package.json b/packages/builder/test/fixtures/collection/library.b/package.json new file mode 100644 index 00000000000..2a0243b1683 --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.b/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.b", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/collection/library.b/src/library/b/.library b/packages/builder/test/fixtures/collection/library.b/src/library/b/.library new file mode 100644 index 00000000000..7128151f3f4 --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.b/src/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + Some fancy copyright ${currentYear} + ${version} + + Library B + + + + library.d + + + + diff --git a/packages/builder/test/fixtures/collection/library.b/test/library/b/Test.html b/packages/builder/test/fixtures/collection/library.b/test/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/collection/library.b/ui5.yaml b/packages/builder/test/fixtures/collection/library.b/ui5.yaml new file mode 100644 index 00000000000..3275ac753b5 --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.b/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.b diff --git a/packages/builder/test/fixtures/collection/library.c/src/library/c/.library b/packages/builder/test/fixtures/collection/library.c/src/library/c/.library new file mode 100644 index 00000000000..4180ce2af2f --- /dev/null +++ b/packages/builder/test/fixtures/collection/library.c/src/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + ${version} + + Library C + + + + library.d + + + + diff --git a/packages/builder/test/fixtures/collection/library.c/test/library/c/Test.html b/packages/builder/test/fixtures/collection/library.c/test/library/c/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/collection/package.json b/packages/builder/test/fixtures/collection/package.json new file mode 100644 index 00000000000..81b948438bd --- /dev/null +++ b/packages/builder/test/fixtures/collection/package.json @@ -0,0 +1,18 @@ +{ + "name": "collection", + "version": "1.0.0", + "description": "Simple Collection", + "dependencies": { + "library.d": "file:../library.d" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "collection": { + "modules": { + "library.a": "./library.a", + "library.b": "./library.b", + "library.c": "./library.c" + } + } +} diff --git a/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require.js b/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require.js new file mode 100644 index 00000000000..ac90f92618b --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require.js @@ -0,0 +1,11 @@ +sap.ui.define([], function() { + return { + load: function(modName) { + sap.ui.require([modName], function(modExport) { + // module was loaded + }, function(err) { + // error occurred + }); + } + } +}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require_sync.js b/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require_sync.js new file mode 100644 index 00000000000..fdd4a37292c --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_dynamic_require_sync.js @@ -0,0 +1,7 @@ +sap.ui.define([], function() { + return { + load: function(modName) { + return sap.ui.requireSync(modName); + } + } +}); diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_first_unnamed.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_first_unnamed.js new file mode 100644 index 00000000000..dc450fff964 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_first_unnamed.js @@ -0,0 +1,15 @@ +/* amd-multiple-modules-first-unnamed + * + * Multiple modules, the first one unnamed, the others having names that don't match the file name. + * The unnamed one should be interpreted as the main module, its documentation should be used + * as module documentation. + */ +sap.ui.define(["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper2", ["./dep1","../dep2"], function() {}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_other_than_first_one_unnamed.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_other_than_first_one_unnamed.js new file mode 100644 index 00000000000..ce518d82395 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_other_than_first_one_unnamed.js @@ -0,0 +1,19 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper2", ["./dep1","../dep2"], function() {}); + +/* amd-multiple-modules-other-than-first-one-unnamed + * + * Multiple modules, the first one unnamed, the others having names that don't match the file name. + * The unnamed one should be interpreted as the main module, its documentation should be used + * as module documentation. + * + * The dependencies of the different modules contain special AMD dependencies. + * They should not be listed in the dependency info. + */ +sap.ui.define(["./dep1","../dep2"], function() {}); diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_named_and_unnamed.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_named_and_unnamed.js new file mode 100644 index 00000000000..5d025018be7 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_named_and_unnamed.js @@ -0,0 +1,16 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* amd-multiple-modules-with-conflict-between-named-and-unnamed + * + * Multiple modules, named and unnamed, the id of one of the named modules matches + * the default id derived from the file path. A later module is unnamed. + * Analyzing the file must throw an exception because of the conflict between the named + * and unnamed module. + */ +sap.ui.define("modules/amd_multiple_modules_with_conflict_between_named_and_unnamed", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define(["./dep1","../dep2"], function() {}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_two_named.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_two_named.js new file mode 100644 index 00000000000..86ec2fafb9d --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_two_named.js @@ -0,0 +1,16 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* amd-multiple-modules-with-conflict-between-two-named + * + * Multiple named modules, the id of more than one of them matches + * the default id derived from the file path. + * Analyzing the file must throw an exception because of the conflict between the two named + * modules. + */ +sap.ui.define("modules/amd_multiple_modules_with_conflict_between_two_named", ["./dep2","../dep1"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("modules/amd_multiple_modules_with_conflict_between_two_named", ["./dep1","../dep2"], function() {}); diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_unnamed_and_named.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_unnamed_and_named.js new file mode 100644 index 00000000000..e5324624b57 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_modules_with_conflict_between_unnamed_and_named.js @@ -0,0 +1,16 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* amd-multiple-modules-with-conflict-between-unnamed-and-named + * + * Multiple modules, named and unnamed, the id of one of the named modules matches + * the default id derived from the file path. An earlier module is unnamed. + * Analyzing the file must throw an exception because of the conflict between the named + * and unnamed module. + */ +sap.ui.define(["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("modules/amd_multiple_modules_with_conflict_between_unnamed_and_named", ["./dep1","../dep2"], function() {}); diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_none_matching_filename.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_none_matching_filename.js new file mode 100644 index 00000000000..bf914ca322d --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_none_matching_filename.js @@ -0,0 +1,12 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper3", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper2", ["./dep1","../dep2"], function() {}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_one_matching_filename.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_one_matching_filename.js new file mode 100644 index 00000000000..865293ea6af --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_named_modules_one_matching_filename.js @@ -0,0 +1,15 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2"], function() {}); +/* amd-multiple-named-modules-one-matching-filename + * + * Multiple named modules, the id of one of them matches the default id derived from the file path. + * That module should be interpreted as the main module, its documentation should be used + * as module documentation. + */ +sap.ui.define("modules/amd_multiple_named_modules_one_matching_filename", ["./dep1","../dep2"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper2", ["./dep1","../dep2"], function() {}); \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/amd_multiple_unnamed_modules.js b/packages/builder/test/fixtures/lbt/modules/amd_multiple_unnamed_modules.js new file mode 100644 index 00000000000..7f1c44e00cc --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_multiple_unnamed_modules.js @@ -0,0 +1,8 @@ +/* + * A file with multiple unnamed modules should cause an exception when being analyzed. + * The exception should indicate that only one module can omit the name, the others must be named. + */ +sap.ui.define(["./dep1","../dep2"], function() {}); +sap.ui.define("utils/helper1",["./dep1","../dep2"], function() {}); +sap.ui.define(["./dep1","../dep2"], function() {}); +sap.ui.define("utils/helper2",["./dep1","../dep2"], function() {}); diff --git a/packages/builder/test/fixtures/lbt/modules/amd_single_named_module.js b/packages/builder/test/fixtures/lbt/modules/amd_single_named_module.js new file mode 100644 index 00000000000..59894c99c93 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_single_named_module.js @@ -0,0 +1,7 @@ +/* amd-single-named-module + * + * A single named module definition. Its name should replace the default name, + * its documentation should be used as module documentation. + */ +sap.ui.define("alternative/name", ["./dep1", "../dep2"], function() {}); + diff --git a/packages/builder/test/fixtures/lbt/modules/amd_single_unnamed_module.js b/packages/builder/test/fixtures/lbt/modules/amd_single_unnamed_module.js new file mode 100644 index 00000000000..2ea8be98076 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_single_unnamed_module.js @@ -0,0 +1,9 @@ +/* amd-single-unnamed-module + * + * A single unnamed module definition. + * It should be recognized as the main module, its name should be the name derived fro mthe filename, + * its documentation should be used as module documentation. + */ +sap.ui.define(["./dep1","../dep2"], function() {}); + + diff --git a/packages/builder/test/fixtures/lbt/modules/amd_special_dependencies.js b/packages/builder/test/fixtures/lbt/modules/amd_special_dependencies.js new file mode 100644 index 00000000000..9ba27a791a9 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/amd_special_dependencies.js @@ -0,0 +1,20 @@ +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper1", ["./dep1","../dep2", "exports"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper2", ["require", "./dep1","../dep2"], function() {}); + +/* amd-special-dependencies + * + * When the dependencies contain one or more of the AMD special dependencies, + * they should be ignored and not be listed in the dependency info. + */ +sap.ui.define(["./dep1","../dep2", "module"], function() {}); +/* + * irrelevant documentation + */ +sap.ui.define("utils/helper3", ["require", "exports", "module"], function() {}); + diff --git a/packages/builder/test/fixtures/lbt/modules/bundle-evo.js b/packages/builder/test/fixtures/lbt/modules/bundle-evo.js new file mode 100644 index 00000000000..628e9c722e8 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/bundle-evo.js @@ -0,0 +1,40 @@ +//@ui5-bundle sap-ui-core.js +window["sap-ui-optimized"] = true; +try { +//@ui5-bundle-raw-include sap/ui/thirdparty/baseuri.js +if(!('baseURI'in Node.prototype)){} +//@ui5-bundle-raw-include sap/ui/thirdparty/es6-promise.js +(function(g,f){ +g.ES6Promise=f();}(this,(function(){}))); +//@ui5-bundle-raw-include sap/ui/thirdparty/es6-shim-nopromise.js +(function(r,f){}()); +//@ui5-bundle-raw-include ui5loader.js +(function(__global){}(window)); +//@ui5-bundle-raw-include ui5loader-autoconfig.js +(function(){"use strict"; +var h=u._.defineModuleSync; +h('ui5loader.js',null); +}()); + +sap.ui.predefine('jquery.sap.global',["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log","sap/ui/dom/getComputedStyleFix","sap/ui/dom/activeElementFix","sap/ui/dom/includeScript","sap/ui/dom/includeStylesheet","sap/ui/core/support/Hotkeys","sap/ui/security/FrameOptions","sap/ui/performance/Measurement","sap/ui/performance/trace/Interaction","sap/ui/base/syncXHRFix","sap/base/util/LoaderExtensions","sap/ui/Device","sap/ui/thirdparty/URI","sap/ui/thirdparty/jquery","sap/ui/thirdparty/jqueryui/jquery-ui-position","ui5loader-autoconfig","jquery.sap.stubs"],function(now,Version,assert,Log,getComputedStyleFix,activeElementFix,includeScript,includeStylesheet,SupportHotkeys,FrameOptions,Measurement,Interaction,syncXHRFix,LoaderExtensions,Device,URI,jQuery){ +}); +sap.ui.predefine('jquery.sap.stubs',["sap/base/Log","sap/base/util/defineLazyProperty","sap/ui/thirdparty/jquery"],function(){}); +sap.ui.predefine('sap/base/Log',["sap/base/util/now"],function(){}); +sap.ui.predefine('sap/base/assert',["./Log"],function(){}); +sap.ui.predefine('sap/ui/base/DataType',['sap/base/util/ObjectPath',"sap/base/assert","sap/base/Log","sap/base/util/isPlainObject"],function(){},true); +sap.ui.predefine('sap/ui/base/Event',['./Object',"sap/base/assert"],function(){}); +sap.ui.predefine('sap/ui/base/ManagedObject',['./BindingParser','./DataType','./EventProvider','./ManagedObjectMetadata','./Object','../model/BindingMode','../model/StaticBinding','../model/CompositeBinding','../model/Context','../model/FormatException','../model/ParseException','../model/Type','../model/ValidateException',"sap/ui/base/SyncPromise","sap/ui/util/ActivityDetection","sap/base/util/ObjectPath","sap/base/Log","sap/base/assert","sap/base/util/deepClone","sap/base/util/deepEqual","sap/base/util/uid","sap/ui/thirdparty/jquery"],function(){}); +sap.ui.predefine('sap/ui/core/Core',['jquery.sap.global','sap/ui/Device','sap/ui/Global','sap/ui/base/BindingParser','sap/ui/base/DataType','sap/ui/base/EventProvider','sap/ui/base/Interface','sap/ui/base/Object','sap/ui/base/ManagedObject','./Component','./Configuration','./Control','./Element','./ElementMetadata','./FocusHandler','./RenderManager','./ResizeHandler','./ThemeCheck','./UIArea','./message/MessageManager',"sap/ui/util/ActivityDetection","sap/ui/dom/getScrollbarSize","sap/base/i18n/ResourceBundle","sap/base/Log","sap/ui/performance/Measurement","sap/ui/security/FrameOptions","sap/base/assert","sap/ui/dom/includeStylesheet","sap/base/util/ObjectPath","sap/base/util/Version","sap/base/util/array/uniqueSort","sap/base/util/uid",'sap/ui/performance/trace/initTraces','sap/base/util/LoaderExtensions','sap/base/util/isEmptyObject','sap/base/util/each','sap/ui/events/jquery/EventSimulation'],function(){}()); +sap.ui.require.preload({ + "sap/ui/thirdparty/URI.js":function(){}, + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js":function(){}, + "sap/ui/Device.js":function(){}, + "sap/ui/thirdparty/jquery.js":function(){}, + "sap/ui/thirdparty/jquery-mobile-custom.js":function(){} +},"sap-ui-core-preload"); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore().boot && sap.ui.getCore().boot(); +} catch(oError) { +if (oError.name != "Restart") { throw oError; } +} diff --git a/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle-v2.js b/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle-v2.js new file mode 100644 index 00000000000..08ed4c434ad --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle-v2.js @@ -0,0 +1,53 @@ +window["sap-ui-optimized"] = true; +try { +// sap/ui/Device +if(window.jQuery&&window.jQuery.sap&&window.jQuery.sap.declare){window.jQuery.sap.declare("sap.ui.Device",false);} +(function(){"use strict";var d={};if(sap.ui.define){sap.ui.define("sap/ui/Device",[],function(){return d;});}}()); +// sap/ui/thirdparty/URI.js +(function(r,f){ +if(typeof exports==='object'){module.exports=f(require('./punycode'),require('./IPv6'),require('./SecondLevelDomains'));} +else if(typeof define==='function'&&define.amd){r.URI=f(r.punycode,r.IPv6,r.SecondLevelDomains,r);define('sap/ui/thirdparty/URI',[],function(){return r.URI;});} +else{r.URI=f(r.punycode,r.IPv6,r.SecondLevelDomains,r);} +}(this,function(a,I,S,r){})); +// sap/ui/thirdparty/es6-promise.js +(function(){ +if(typeof define==='function'&&define['amd']){define('sap/ui/thirdparty/es6-promise',function(){return {};});} +else if(typeof module!=='undefined'&&module['exports']){module['exports']={};} +if(typeof this!=='undefined'){this['ES6Promise']={};}}).call(this); +// sap/ui/thirdparty/jquery.js +(function(g,f){}()); +// sap/ui/thirdparty/jqueryui/jquery-ui-position.js +(function($){$.ui=$.ui||{};}(jQuery)); +// jquery.sap.global.js +(function(){}()); +jQuery.sap.globalEval=function(){"use strict";eval(arguments[0]);}; +jQuery.sap.declare('sap-ui-core'); +jQuery.sap.declare('sap.ui.Device', false); +jQuery.sap.declare('sap.ui.thirdparty.URI', false); +jQuery.sap.declare('sap.ui.thirdparty.es6-promise', false); +jQuery.sap.declare('sap.ui.thirdparty.jquery', false); +jQuery.sap.declare('sap.ui.thirdparty.jqueryui.jquery-ui-position', false); +jQuery.sap.declare('jquery.sap.global', false); +sap.ui.predefine('jquery.sap.act',['jquery.sap.global'],function(q){}); +sap.ui.predefine('jquery.sap.encoder',['jquery.sap.global'],function(q){}); +sap.ui.predefine('jquery.sap.events',['jquery.sap.global','sap/ui/Device','jquery.sap.keycodes','jquery.sap.dom','jquery.sap.script',"sap/ui/thirdparty/jquery-mobile-custom"],function(){}); +sap.ui.predefine('jquery.sap.keycodes',['jquery.sap.global'],function(){}); +sap.ui.predefine('sap/ui/base/DataType',['jquery.sap.global'],function(){}, true); +sap.ui.predefine('sap/ui/base/Event',['jquery.sap.global','./Object'],function(){}); +sap.ui.predefine('sap/ui/base/ManagedObject',['jquery.sap.global','./BindingParser','./DataType','./EventProvider','./ManagedObjectMetadata','../model/BindingMode','../model/CompositeBinding','../model/Context','../model/FormatException','../model/ListBinding','../model/Model','../model/ParseException','../model/TreeBinding','../model/Type','../model/ValidateException','jquery.sap.act','jquery.sap.script','jquery.sap.strings'],function(){}); +sap.ui.predefine('sap/ui/core/Core',['jquery.sap.global','sap/ui/Device','sap/ui/Global','sap/ui/base/BindingParser','sap/ui/base/DataType','sap/ui/base/EventProvider','sap/ui/base/Interface','sap/ui/base/Object','sap/ui/base/ManagedObject','./Component','./Configuration','./Control','./Element','./ElementMetadata','./FocusHandler','./RenderManager','./ResizeHandler','./ThemeCheck','./UIArea','./message/MessageManager','jquery.sap.act','jquery.sap.dom','jquery.sap.events','jquery.sap.mobile','jquery.sap.properties','jquery.sap.resources','jquery.sap.script','jquery.sap.sjax'],function(){}); +jQuery.sap.registerPreloadedModules({ +"name":"sap-ui-core-preload", +"modules":{ + "sap.ui.thirdparty.jquery-mobile-custom":function(){ + (function(r,d,f){ + if(typeof define==="function"&&define.amd){define(["jquery"],function($){f($,r,d);return $.mobile;});} + else{f(r.jQuery,r,d);}}()); + } +}}); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore().boot && sap.ui.getCore().boot(); +} catch(oError) { +if (oError.name != "Restart") { throw oError; } +} diff --git a/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle.js b/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle.js new file mode 100644 index 00000000000..97a6b789f3e --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/bundle-oldstyle.js @@ -0,0 +1,54 @@ +window["sap-ui-optimized"] = true; +try { +// sap/ui/Device +if(window.jQuery&&window.jQuery.sap&&window.jQuery.sap.declare){window.jQuery.sap.declare("sap.ui.Device",false);} +(function(){"use strict";var d={};if(sap.ui.define){sap.ui.define("sap/ui/Device",[],function(){return d;});}}()); +// sap/ui/thirdparty/URI.js +(function(r,f){ +if(typeof exports==='object'){module.exports=f(require('./punycode'),require('./IPv6'),require('./SecondLevelDomains'));} +else if(typeof define==='function'&&define.amd){r.URI=f(r.punycode,r.IPv6,r.SecondLevelDomains,r);define('sap/ui/thirdparty/URI',[],function(){return r.URI;});} +else{r.URI=f(r.punycode,r.IPv6,r.SecondLevelDomains,r);} +}(this,function(a,I,S,r){})); +// sap/ui/thirdparty/es6-promise.js +(function(){ +if(typeof define==='function'&&define['amd']){define('sap/ui/thirdparty/es6-promise',function(){return {};});} +else if(typeof module!=='undefined'&&module['exports']){module['exports']={};} +if(typeof this!=='undefined'){this['ES6Promise']={};}}).call(this); +// sap/ui/thirdparty/jquery.js +(function(g,f){}()); +// sap/ui/thirdparty/jqueryui/jquery-ui-position.js +(function($){$.ui=$.ui||{};}(jQuery)); +// jquery.sap.global.js +(function(){}()); +jQuery.sap.globalEval=function(){"use strict";eval(arguments[0]);}; +jQuery.sap.declare('sap-ui-core'); +jQuery.sap.declare('sap.ui.Device', false); +jQuery.sap.declare('sap.ui.thirdparty.URI', false); +jQuery.sap.declare('sap.ui.thirdparty.es6-promise', false); +jQuery.sap.declare('sap.ui.thirdparty.jquery', false); +jQuery.sap.declare('sap.ui.thirdparty.jqueryui.jquery-ui-position', false); +jQuery.sap.declare('jquery.sap.global', false); +sap.ui.predefine('jquery.sap.act',['jquery.sap.global'],function(q){}); +sap.ui.predefine('jquery.sap.encoder',['jquery.sap.global'],function(q){}); +sap.ui.predefine('jquery.sap.events',['jquery.sap.global','sap/ui/Device','jquery.sap.keycodes','jquery.sap.dom','jquery.sap.script',"sap/ui/thirdparty/jquery-mobile-custom"],function(){}); +sap.ui.predefine('jquery.sap.keycodes',['jquery.sap.global'],function(){}); +sap.ui.predefine('sap/ui/base/DataType',['jquery.sap.global'],function(){}, true); +sap.ui.predefine('sap/ui/base/Event',['jquery.sap.global','./Object'],function(){}); +sap.ui.predefine('sap/ui/base/ManagedObject',['jquery.sap.global','./BindingParser','./DataType','./EventProvider','./ManagedObjectMetadata','../model/BindingMode','../model/CompositeBinding','../model/Context','../model/FormatException','../model/ListBinding','../model/Model','../model/ParseException','../model/TreeBinding','../model/Type','../model/ValidateException','jquery.sap.act','jquery.sap.script','jquery.sap.strings'],function(){}); +sap.ui.predefine('sap/ui/core/Core',['jquery.sap.global','sap/ui/Device','sap/ui/Global','sap/ui/base/BindingParser','sap/ui/base/DataType','sap/ui/base/EventProvider','sap/ui/base/Interface','sap/ui/base/Object','sap/ui/base/ManagedObject','./Component','./Configuration','./Control','./Element','./ElementMetadata','./FocusHandler','./RenderManager','./ResizeHandler','./ThemeCheck','./UIArea','./message/MessageManager','jquery.sap.act','jquery.sap.dom','jquery.sap.events','jquery.sap.mobile','jquery.sap.properties','jquery.sap.resources','jquery.sap.script','jquery.sap.sjax'],function(){}); +jQuery.sap.registerPreloadedModules({ +"name":"sap-ui-core-preload", +"version":"2.0", +"modules":{ + "sap/ui/thirdparty/jquery-mobile-custom.js":function(){ + (function(r,d,f){ + if(typeof define==="function"&&define.amd){define(["jquery"],function($){f($,r,d);return $.mobile;});} + else{f(r.jQuery,r,d);}}()); + } +}}); +sap.ui.requireSync("sap/ui/core/Core"); +// as this module contains the Core, we ensure that the Core has been booted +sap.ui.getCore().boot && sap.ui.getCore().boot(); +} catch(oError) { +if (oError.name != "Restart") { throw oError; } +} diff --git a/packages/builder/test/fixtures/lbt/modules/bundle.js b/packages/builder/test/fixtures/lbt/modules/bundle.js new file mode 100644 index 00000000000..2ee4601c9a3 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/bundle.js @@ -0,0 +1,12 @@ +sap.ui.predefine("sap/m/CheckBox",["sap/ui/core/Control"],function(o){"use strict";var n=o.extend("sap.m.CheckBox");return n}); +sap.ui.predefine("sap/ui/core/Core",[],function(){"use strict";return {}}); +sap.ui.predefine("todo/Component",["sap/ui/core/UIComponent"],function(e){"use strict";return e.extend("todo.Component",{metadata:{manifest:"json"}})}); +sap.ui.predefine("todo/controller/App.controller",["sap/ui/core/mvc/Controller"],function(e){"use strict";return e.extend("todo.controller.App")}); +jQuery.sap.registerPreloadedModules({ +"version":"2.0", +"modules":{ + "sap/m/messagebundle.properties":'#This is the resource bundle for the SAPUI5 sar\r\n', + "todo/manifest.json":'{"sap.app":{"id":"todo","type":"application"},"sap.ui5":{"rootView":{"viewName":"todo.view.App","type":"XML","id":"app"},"models":{"i18n":{"type":"sap.ui.model.resource.ResourceModel","settings":{"bundleName":"todo.i18n.messageBundle"}},"":{"type":"sap.ui.model.json.JSONModel","settings":"/model/todoitems.json"}},"resources":{"css":[{"uri":"css/styles.css"}]}}}', + "todo/model/todoitems.json":'{"newTodo":"","todos":[{"title":"Start this app","completed":true},{"title":"Learn OpenUI5","completed":false}],"itemsRemovable":true,"someCompleted":true,"completedCount":1}', + "todo/view/App.view.xml":'
    \n' +}}); diff --git a/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require.js b/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require.js new file mode 100644 index 00000000000..d29d573adf4 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require.js @@ -0,0 +1,5 @@ +jQuery.sap.declare("sap.ui.testmodule"); + +sap.ui.testmodule.load = function(modName) { + jQuery.sap.require(modName); +}; diff --git a/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require_conditional.js b/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require_conditional.js new file mode 100644 index 00000000000..9849ee03bf4 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_dynamic_require_conditional.js @@ -0,0 +1,3 @@ +jQuery.sap.declare("sap.ui.testmodule"); +const Foo = "conditional/module2"; +jQuery.sap.require(true ? "conditional/module1" : Foo); diff --git a/packages/builder/test/fixtures/lbt/modules/declare_function_expr_scope.js b/packages/builder/test/fixtures/lbt/modules/declare_function_expr_scope.js new file mode 100644 index 00000000000..661eb022477 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_function_expr_scope.js @@ -0,0 +1,43 @@ +// uses a function expr + additional arguments to create a scope +// Note that this scope style is old-fashioned and results in a JSLint warning! +(function($1, window) { + + //declares module sap.ui.testmodule + jQuery.sap.declare("sap.ui.testmodule"); + + // top level statements in the scope + jQuery.sap.require("top.require.void"); + var x = jQuery.sap.require("top.require.var"); + x = jQuery.sap.require("top.require.assign"); + var xs = sap.ui.requireSync("top/requireSync/var"); + xs = sap.ui.requireSync("top/requireSync/assign"); + + // a block with require statements + { + jQuery.sap.require("block.require.void"); + var z = jQuery.sap.require("block.require.var"); + z = jQuery.sap.require("block.require.assign"); + var zs = sap.ui.requireSync("block/requireSync/var"); + zs = sap.ui.requireSync("block/requireSync/assign"); + } + + // a nested function invocation with require statements + (function() { + jQuery.sap.require("nested.scope.require.void"); + var z = jQuery.sap.require("nested.scope.require.var"); + z = jQuery.sap.require("nested.scope.require.assign"); + var zs = sap.ui.requireSync("nested/scope/requireSync/var"); + zs = sap.ui.requireSync("nested/scope/requireSync/assign"); + }()); + + //a nested function expression with require statements + (function() { + jQuery.sap.require("nested.scope2.require.void"); + var z = jQuery.sap.require("nested.scope2.require.var"); + z = jQuery.sap.require("nested.scope2.require.assign"); + var zs = sap.ui.requireSync("nested/scope2/requireSync/var"); + zs = sap.ui.requireSync("nested/scope2/requireSync/assign"); + })(); + +})(jQuery, this); + diff --git a/packages/builder/test/fixtures/lbt/modules/declare_function_invocation_scope.js b/packages/builder/test/fixtures/lbt/modules/declare_function_invocation_scope.js new file mode 100644 index 00000000000..a1ba6cbba41 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_function_invocation_scope.js @@ -0,0 +1,41 @@ +//uses a function invocation expr to create a scope +(function($2, window){ + + //declares module sap.ui.testmodule + jQuery.sap.declare("sap.ui.testmodule"); + + // top level statements in the scope + jQuery.sap.require("top.require.void"); + var x = jQuery.sap.require("top.require.var"); + x = jQuery.sap.require("top.require.assign"); + var xs = sap.ui.requireSync("top/requireSync/var"); + xs = sap.ui.requireSync("top/requireSync/assign"); + + // a block with require statements + { + jQuery.sap.require("block.require.void"); + var z = jQuery.sap.require("block.require.var"); + z = jQuery.sap.require("block.require.assign"); + var zs = sap.ui.requireSync("block/requireSync/var"); + zs = sap.ui.requireSync("block/requireSync/assign"); + } + + // a nested function invocation with require statements + (function() { + jQuery.sap.require("nested.scope.require.void"); + var z = jQuery.sap.require("nested.scope.require.var"); + z = jQuery.sap.require("nested.scope.require.assign"); + var zs = sap.ui.requireSync("nested/scope/requireSync/var"); + zs = sap.ui.requireSync("nested/scope/requireSync/assign"); + }()); + + //a nested function expression with require statements + (function() { + jQuery.sap.require("nested.scope2.require.void"); + var z = jQuery.sap.require("nested.scope2.require.var"); + z = jQuery.sap.require("nested.scope2.require.assign"); + var zs = sap.ui.requireSync("nested/scope2/requireSync/var"); + zs = sap.ui.requireSync("nested/scope2/requireSync/assign"); + })(); + +}(jQuery, this)); diff --git a/packages/builder/test/fixtures/lbt/modules/declare_require_conditional.js b/packages/builder/test/fixtures/lbt/modules/declare_require_conditional.js new file mode 100644 index 00000000000..7ba08c7b533 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_require_conditional.js @@ -0,0 +1,2 @@ +jQuery.sap.declare("sap.ui.testmodule"); +jQuery.sap.require(true ? `conditional/module1` : "conditional/module2"); diff --git a/packages/builder/test/fixtures/lbt/modules/declare_toplevel.js b/packages/builder/test/fixtures/lbt/modules/declare_toplevel.js new file mode 100644 index 00000000000..f849f88de0d --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/declare_toplevel.js @@ -0,0 +1,36 @@ +//declares module sap.ui.testmodule +jQuery.sap.declare("sap.ui.testmodule"); + +// top level statements in the scope +jQuery.sap.require("top.require.void"); +var x = jQuery.sap.require("top.require.var"); +x = jQuery.sap.require("top.require.assign"); +var xs = sap.ui.requireSync("top/requireSync/var"); +xs = sap.ui.requireSync("top/requireSync/assign"); + +// a block with require statements +{ + jQuery.sap.require("block.require.void"); + var z = jQuery.sap.require("block.require.var"); + z = jQuery.sap.require("block.require.assign"); + var zs = sap.ui.requireSync("block/requireSync/var"); + zs = sap.ui.requireSync("block/requireSync/assign"); +} + +// a nested function invocation with require statements +(function() { + jQuery.sap.require("nested.scope.require.void"); + var z = jQuery.sap.require("nested.scope.require.var"); + z = jQuery.sap.require("nested.scope.require.assign"); + var zs = sap.ui.requireSync("nested/scope/requireSync/var"); + zs = sap.ui.requireSync("nested/scope/requireSync/assign"); +}()); + +//a nested function expression with require statements +(function() { + jQuery.sap.require("nested.scope2.require.void"); + var z = jQuery.sap.require("nested.scope2.require.var"); + z = jQuery.sap.require("nested.scope2.require.assign"); + var zs = sap.ui.requireSync("nested/scope2/requireSync/var"); + zs = sap.ui.requireSync("nested/scope2/requireSync/assign"); +})(); diff --git a/packages/builder/test/fixtures/lbt/modules/define_toplevel_named.js b/packages/builder/test/fixtures/lbt/modules/define_toplevel_named.js new file mode 100644 index 00000000000..4f3f19cbcce --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/define_toplevel_named.js @@ -0,0 +1,4 @@ +//declares module sap.ui.testmodule +sap.ui.define("sap/ui/testmodule", ["define/arg1","define/arg2"], function(Strings,Dom,Something) { + +}); diff --git a/packages/builder/test/fixtures/lbt/modules/define_toplevel_unnamed.js b/packages/builder/test/fixtures/lbt/modules/define_toplevel_unnamed.js new file mode 100644 index 00000000000..245c0697a0c --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/define_toplevel_unnamed.js @@ -0,0 +1,4 @@ +//declares module sap.ui.testmodule +sap.ui.define(["define/arg1","define/arg2"], function(Strings,Dom,Something) { + +}); diff --git a/packages/builder/test/fixtures/lbt/modules/define_with_legacy_calls.js b/packages/builder/test/fixtures/lbt/modules/define_with_legacy_calls.js new file mode 100644 index 00000000000..3637bff2dc3 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/define_with_legacy_calls.js @@ -0,0 +1,4 @@ +//declares module sap.ui.testmodule +sap.ui.define(["define/arg1","define/arg2"], function(Strings,Dom,Something) { + jQuery.sap.require("require/arg1"); +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-async-module.js b/packages/builder/test/fixtures/lbt/modules/es6-async-module.js new file mode 100644 index 00000000000..b8c9f786ac1 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-async-module.js @@ -0,0 +1,5 @@ +// async function with await +sap.ui.define([], async () => { + return await sap.ui.require(["static/module1"], async () => {}); + // await and sap.ui.require, makes no sense because it does NOT return a promise but it should still be analyzed +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-syntax-dynamic-dependencies.js b/packages/builder/test/fixtures/lbt/modules/es6-syntax-dynamic-dependencies.js new file mode 100644 index 00000000000..73ce63cd71d --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-syntax-dynamic-dependencies.js @@ -0,0 +1,7 @@ +sap.ui.define([ + 'static/module1' +], () => { + // spread expression, currently not detected as dependency + const dynamicModules = ["not-detected/module1"]; + sap.ui.require(["static/module1", ...dynamicModules]); +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-syntax.js b/packages/builder/test/fixtures/lbt/modules/es6-syntax.js new file mode 100644 index 00000000000..14f27715c5d --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-syntax.js @@ -0,0 +1,95 @@ +sap.ui.define([ + 'static/module1' +], (m1) => { // using an arrow function for the module factory + + sap.ui.require(['static/module2'], function() { + sap.ui.require(["static/module3"], function() {}); + sap.ui.require('no-dependency/module1'); // probing API does not introduce a dependency + }); + + // using an arrow function for the require callback + sap.ui.require([], () => { + sap.ui.require(['static/module4'], () => sap.ui.require(['static/module8'])); + }); + + // default value in array destructuring + let [exp1 = sap.ui.require(['conditional/module1'], function(){})] = []; + + // default value in object destructuring + let {exp2 = sap.ui.require(['conditional/module2'], function(){})} = {}; + + // dependency embedded in a template + let exp3 = `Some text with an embedded dependency ${sap.ui.require(['static/module5'], function(){})} and further text`; + + // dependency embedded in a tagged template + let exp4 = html`Some text with an embedded dependency ${sap.ui.require(['static/module6'], function(){})} and further text`; + + // IIAFE (an immediately invoked arrow function expression) + ((() => { + sap.ui.require(['static/module7'], function(){}); + })()); + + // a not immediately executed arrow function + let helper = (() => { + sap.ui.require(['conditional/module3'], function(){}); + }); + + // async / await + const myFunction = async function() { + await Promise.resolve(); + }; + + // chain expression + sap?.ui?.require(["conditional/module4"]); + + // iterator pattern + const iterator = { + [Symbol.iterator]() { + return { + next () { + sap.ui.require(['conditional/module6']); + return { done: true, value: 1 }; + } + }; + } + } + + // generator pattern + let generator = { + *[Symbol.iterator]() { + for (;;) { + yield sap.ui.require(['conditional/module7']); + } + } + } + + // class declaration + class Clz { + + * bar () { + sap.ui.require(['conditional/module8']); + } + + get conditionalModule() { sap.ui.require(['conditional/module9']) } + + static foo () { + sap.ui.require(['conditional/module10']) + } + + }; + + m1 ?? sap.ui.require(['conditional/module11']); + + // ObjectPattern as Id + const {module11, module12} = { + module11: sap.ui.require(['static/module11'], function(){}), + module12: sap.ui.require(['static/module12'], function(){}) + }; + + // ArrayPattern as Id + const [module13, module14] = [ + sap.ui.require(['static/module13'], function(){}), + sap.ui.require(['static/module14'], function(){}) + ]; + +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-template-literal-predefine.js b/packages/builder/test/fixtures/lbt/modules/es6-template-literal-predefine.js new file mode 100644 index 00000000000..ddc8dccac11 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-template-literal-predefine.js @@ -0,0 +1,4 @@ +// template literal as module name +sap.ui.predefine(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => { + return sap.ui.require([`static/module3`], () => { }); // template literal when loading module +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-template-literal-with-expression.js b/packages/builder/test/fixtures/lbt/modules/es6-template-literal-with-expression.js new file mode 100644 index 00000000000..1cd3604716f --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-template-literal-with-expression.js @@ -0,0 +1,8 @@ +// template literal as module name +sap.ui.define(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => { + const i = 4; + return sap.ui.require([ + `static/module3`, + `not-detected/module${i}` // template literal with expression when loading module, not detected by the JSModuleAnalayzer + ], () => { }); +}); diff --git a/packages/builder/test/fixtures/lbt/modules/es6-template-literal.js b/packages/builder/test/fixtures/lbt/modules/es6-template-literal.js new file mode 100644 index 00000000000..8fcdeb3deeb --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/es6-template-literal.js @@ -0,0 +1,4 @@ +// template literal as module name +sap.ui.define(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => { + return sap.ui.require([`static/module3`], () => { }); // template literal when loading module +}); diff --git a/packages/builder/test/fixtures/lbt/modules/no_declare_but_requires.js b/packages/builder/test/fixtures/lbt/modules/no_declare_but_requires.js new file mode 100644 index 00000000000..a3641d2bce9 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/no_declare_but_requires.js @@ -0,0 +1,4 @@ +jQuery.sap.require("dependency1"); +jQuery.sap.require("dependency2"); +// this module has dependencies, but does not declare a name. +// When such a module is scanned without enough knowledge about the folder structure, the name might be "unknown" (null) \ No newline at end of file diff --git a/packages/builder/test/fixtures/lbt/modules/not_a_module.js b/packages/builder/test/fixtures/lbt/modules/not_a_module.js new file mode 100644 index 00000000000..786cc378cb2 --- /dev/null +++ b/packages/builder/test/fixtures/lbt/modules/not_a_module.js @@ -0,0 +1 @@ +// this file does not contain a UI5 module definition \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/.library b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/.library new file mode 100644 index 00000000000..d6a612a9241 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + 1.0.0 + + Library D + + diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js new file mode 100644 index 00000000000..8af07c6008c --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ + +//# sourceMappingURL=empty.js.map \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js.map b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js.map new file mode 100644 index 00000000000..767ef9fbdc8 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/empty.js.map @@ -0,0 +1 @@ +{"version":3,"file":"empty.js","names":[],"sources":[],"mappings":"","ignoreList":[]} \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js new file mode 100644 index 00000000000..8238eb084f2 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +var topLevelVar="Old World";console.log("Hello "+topLevelVar); +//# sourceMappingURL=legacy.js.map \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js.map b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js.map new file mode 100644 index 00000000000..aa09f6e3c4c --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/legacy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"legacy.js","names":["topLevelVar","console","log"],"sources":["legacy-dbg.js"],"mappings":";;;AAGA,IAAIA,YAAc,YAClBC,QAAQC,IAAI,SAAWF"} \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/manifest.json b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/manifest.json new file mode 100644 index 00000000000..c4f9b3baa72 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.21.0", + "sap.app": { + "id": "library.d", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "Library D", + "description": "Library D", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js new file mode 100644 index 00000000000..75aba1e2383 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js @@ -0,0 +1,5 @@ +/*! + * Some fancy copyright + */ +(function(){var o="World";console.log("Hello "+o)})(); +//# sourceMappingURL=some.js.map \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js.map b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js.map new file mode 100644 index 00000000000..8f900826d79 --- /dev/null +++ b/packages/builder/test/fixtures/library.d-minified/main/src/library/d/some.js.map @@ -0,0 +1 @@ +{"version":3,"file":"some.js","names":["someNonUglifiedVariable","console","log"],"sources":["some-dbg.js"],"mappings":";;;CAGA,WACC,IAAIA,EAA0B,QAC9BC,QAAQC,IAAI,SAAWF,EACvB,EAHD"} \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.d-minified/main/test/library/d/Test.html b/packages/builder/test/fixtures/library.d-minified/main/test/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/library.d/main/src/library/d/.library b/packages/builder/test/fixtures/library.d/main/src/library/d/.library new file mode 100644 index 00000000000..53c2d14c9d6 --- /dev/null +++ b/packages/builder/test/fixtures/library.d/main/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + ${version} + + Library D + + diff --git a/packages/builder/test/fixtures/library.d/main/src/library/d/empty.js b/packages/builder/test/fixtures/library.d/main/src/library/d/empty.js new file mode 100644 index 00000000000..117426a8389 --- /dev/null +++ b/packages/builder/test/fixtures/library.d/main/src/library/d/empty.js @@ -0,0 +1,3 @@ +/*! + * ${copyright} + */ diff --git a/packages/builder/test/fixtures/library.d/main/src/library/d/legacy.js b/packages/builder/test/fixtures/library.d/main/src/library/d/legacy.js new file mode 100644 index 00000000000..d4a7b26fdf4 --- /dev/null +++ b/packages/builder/test/fixtures/library.d/main/src/library/d/legacy.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +var topLevelVar = "Old World"; +console.log('Hello ' + topLevelVar); diff --git a/packages/builder/test/fixtures/library.d/main/src/library/d/library.js b/packages/builder/test/fixtures/library.d/main/src/library/d/library.js new file mode 100644 index 00000000000..678cb80349b --- /dev/null +++ b/packages/builder/test/fixtures/library.d/main/src/library/d/library.js @@ -0,0 +1,23 @@ +/* + * ${copyright} + */ + +sap.ui.define([ + "sap/ui/core/Lib", + "sap/ui/core/library" +], function (Library) { + "use strict"; + + return Library.init({ + name: "library.d", + version: "${version}", + dependencies: [ + "sap.ui.core" + ], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: true + }); +}); diff --git a/packages/builder/test/fixtures/library.d/main/src/library/d/some.js b/packages/builder/test/fixtures/library.d/main/src/library/d/some.js new file mode 100644 index 00000000000..7c7022e462c --- /dev/null +++ b/packages/builder/test/fixtures/library.d/main/src/library/d/some.js @@ -0,0 +1,8 @@ +/*! + * ${copyright} + */ + +sap.ui.define(["./library"], (_library) => { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +}); diff --git a/packages/builder/test/fixtures/library.d/main/test/library/d/Test.html b/packages/builder/test/fixtures/library.d/main/test/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/library.e/src/library/e/.library b/packages/builder/test/fixtures/library.e/src/library/e/.library new file mode 100644 index 00000000000..26ff954f7b1 --- /dev/null +++ b/packages/builder/test/fixtures/library.e/src/library/e/.library @@ -0,0 +1,11 @@ + + + + library.e + SAP SE + ${copyright} + ${version} + + Library E + + diff --git a/packages/builder/test/fixtures/library.e/src/library/e/library.js b/packages/builder/test/fixtures/library.e/src/library/e/library.js new file mode 100644 index 00000000000..e6676c815d9 --- /dev/null +++ b/packages/builder/test/fixtures/library.e/src/library/e/library.js @@ -0,0 +1,6 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); + +// ${version} diff --git a/packages/builder/test/fixtures/library.e/src/library/e/manifest.json b/packages/builder/test/fixtures/library.e/src/library/e/manifest.json new file mode 100644 index 00000000000..01221dedc26 --- /dev/null +++ b/packages/builder/test/fixtures/library.e/src/library/e/manifest.json @@ -0,0 +1,28 @@ +{ + "_version": "1.1.0", + "sap.app": { + "id": "library.e", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "${version}" + }, + "title": "Library E", + "description": "Library E", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.0", + "libs": {} + }, + "library": { + "i18n": false + } + } +} diff --git a/packages/builder/test/fixtures/library.e/src/library/e/some.js b/packages/builder/test/fixtures/library.e/src/library/e/some.js new file mode 100644 index 00000000000..81e73436075 --- /dev/null +++ b/packages/builder/test/fixtures/library.e/src/library/e/some.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.e/test/library/e/Test.css b/packages/builder/test/fixtures/library.e/test/library/e/Test.css new file mode 100644 index 00000000000..3f46b4c60cb --- /dev/null +++ b/packages/builder/test/fixtures/library.e/test/library/e/Test.css @@ -0,0 +1,4 @@ +/* +${copyright} +${version} +*/ diff --git a/packages/builder/test/fixtures/library.e/test/library/e/Test.html b/packages/builder/test/fixtures/library.e/test/library/e/Test.html new file mode 100644 index 00000000000..be546dc059a --- /dev/null +++ b/packages/builder/test/fixtures/library.e/test/library/e/Test.html @@ -0,0 +1,4 @@ + diff --git a/packages/builder/test/fixtures/library.e/test/library/e/Test.js b/packages/builder/test/fixtures/library.e/test/library/e/Test.js new file mode 100644 index 00000000000..3f46b4c60cb --- /dev/null +++ b/packages/builder/test/fixtures/library.e/test/library/e/Test.js @@ -0,0 +1,4 @@ +/* +${copyright} +${version} +*/ diff --git a/packages/builder/test/fixtures/library.f/package.json b/packages/builder/test/fixtures/library.f/package.json new file mode 100644 index 00000000000..d8f009d42d7 --- /dev/null +++ b/packages/builder/test/fixtures/library.f/package.json @@ -0,0 +1,11 @@ +{ + "name": "library.f", + "version": "1.0.0", + "description": "Simple SAPUI5 based library - test for circular dependencies", + "dependencies": { + "library.g": "file:../library.g" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.f/ui5.yaml b/packages/builder/test/fixtures/library.f/ui5.yaml new file mode 100644 index 00000000000..38440bacbc6 --- /dev/null +++ b/packages/builder/test/fixtures/library.f/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.f diff --git a/packages/builder/test/fixtures/library.g/package.json b/packages/builder/test/fixtures/library.g/package.json new file mode 100644 index 00000000000..fff39011db1 --- /dev/null +++ b/packages/builder/test/fixtures/library.g/package.json @@ -0,0 +1,11 @@ +{ + "name": "library.g", + "version": "1.0.0", + "description": "Simple SAPUI5 based library - test for circular dependencies", + "devDependencies": { + "library.f": "file:../library.f" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/.library b/packages/builder/test/fixtures/library.h/main/src/library/h/.library new file mode 100644 index 00000000000..38501ff4085 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/.library @@ -0,0 +1,19 @@ + + + + library.h + SAP SE + Some fancy copyright + ${version} + + Library H + + + + + + + + + + diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/components/Component.js b/packages/builder/test/fixtures/library.h/main/src/library/h/components/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/components/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/components/TodoComponent.js b/packages/builder/test/fixtures/library.h/main/src/library/h/components/TodoComponent.js new file mode 100644 index 00000000000..c3808100497 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/components/TodoComponent.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log(' File '); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent1/Component.js b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent1/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent1/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent2/Component.js b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent2/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent2/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent3/Component.js b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent3/Component.js new file mode 100644 index 00000000000..998e27dcec6 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/components/subcomponent3/Component.js @@ -0,0 +1,5 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.g.Component', { + }); +}); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js b/packages/builder/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js new file mode 100644 index 00000000000..e961048c1d3 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js @@ -0,0 +1,14 @@ +/*! + * ${copyright} + */ + +/** + * designtime and global export + */ +var myexport = (function() { + + "use strict"; + + String("asd"); + +}()); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/file.js b/packages/builder/test/fixtures/library.h/main/src/library/h/file.js new file mode 100644 index 00000000000..c3808100497 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/file.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log(' File '); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties b/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties b/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties new file mode 100644 index 00000000000..1c6a1a9e8f5 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties @@ -0,0 +1 @@ +a=b \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/library.js b/packages/builder/test/fixtures/library.h/main/src/library/h/library.js new file mode 100644 index 00000000000..c5b4fdcfdf4 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/library.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log(' Library '); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/not.js b/packages/builder/test/fixtures/library.h/main/src/library/h/not.js new file mode 100644 index 00000000000..778bfa1f5a5 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/not.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log(' Not including '); diff --git a/packages/builder/test/fixtures/library.h/main/src/library/h/some.js b/packages/builder/test/fixtures/library.h/main/src/library/h/some.js new file mode 100644 index 00000000000..c1a44398b92 --- /dev/null +++ b/packages/builder/test/fixtures/library.h/main/src/library/h/some.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +//@ui5-bundle-raw-include library/h/other.js +console.log(' Some '); diff --git a/packages/builder/test/fixtures/library.h/main/test/library/h/Test.html b/packages/builder/test/fixtures/library.h/main/test/library/h/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/library.i/main/src/library/i/.library b/packages/builder/test/fixtures/library.i/main/src/library/i/.library new file mode 100644 index 00000000000..e035afe77ab --- /dev/null +++ b/packages/builder/test/fixtures/library.i/main/src/library/i/.library @@ -0,0 +1,44 @@ + + + + library.i + SAP SE + ${copyright} + ${version} + + {{title}} + {{description}} + + + + sap.ui.core + + + library.d + true + + + + + + i18nf/custommessagebundle.properties + false + + sap_belize + sap_belize_plus + sap_belize_hcw + sap_belize_hcb + + + + + F0042 + fancycomponent + + + + NOT-ME + + + + diff --git a/packages/builder/test/fixtures/library.i/main/src/library/i/i18nf/custommessagebundle.properties b/packages/builder/test/fixtures/library.i/main/src/library/i/i18nf/custommessagebundle.properties new file mode 100644 index 00000000000..dbb825dabd8 --- /dev/null +++ b/packages/builder/test/fixtures/library.i/main/src/library/i/i18nf/custommessagebundle.properties @@ -0,0 +1 @@ +mytext=Hello \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.i/main/src/library/i/library.js b/packages/builder/test/fixtures/library.i/main/src/library/i/library.js new file mode 100644 index 00000000000..e1c0e2cb058 --- /dev/null +++ b/packages/builder/test/fixtures/library.i/main/src/library/i/library.js @@ -0,0 +1,38 @@ +/*! + * ${copyright} + */ +sap.ui.define([ + 'sap/ui/core/Core', +], function(Core) { + + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.i", + version: "${version}", + dependencies : ["sap.ui.core"], + types: [ + "library.i.ButtonType", + "library.i.DialogType", + ], + interfaces: [ + "library.i.IContent", + ], + controls: [ + "library.i.Button", + "library.i.CheckBox", + "library.i.Dialog", + "library.i.Input", + "library.i.Label", + "library.i.Link", + "library.i.Menu", + "library.i.Text" + ], + elements: [ + "library.i.MenuItem" + ], + }); + + return thisLib; + +}); diff --git a/packages/builder/test/fixtures/library.i/package.json b/packages/builder/test/fixtures/library.i/package.json new file mode 100644 index 00000000000..f930ef082d2 --- /dev/null +++ b/packages/builder/test/fixtures/library.i/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.h", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/.library b/packages/builder/test/fixtures/library.j/main/src/library/j/.library new file mode 100644 index 00000000000..1919da37e58 --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/.library @@ -0,0 +1,11 @@ + + + + library.j + SAP SE + Some fancy copyright + ${version} + + Library J + + diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/Foo.js b/packages/builder/test/fixtures/library.j/main/src/library/j/Foo.js new file mode 100644 index 00000000000..1bec63ae1cb --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/Foo.js @@ -0,0 +1,31 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunction + * - ChainExpression + * - ClassDeclaration + */ +(sap?.ui).define([`Bar`], (Bar) => { + /** + * @class + * My super documentation of this class + * + * @extends library.j.Bar + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.Foo + */ + class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + } + + return Foo; +}); diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/SubControl.js b/packages/builder/test/fixtures/library.j/main/src/library/j/SubControl.js new file mode 100644 index 00000000000..283fcaf16c7 --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/SubControl.js @@ -0,0 +1,45 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunctionExpression + */ + window.someRandomModule || + sap.ui.define( + ["sap/ui/core/Control"], + /** + * Constructor for a new library.j.SubControl. + * + * @param {string} [sId] ID for the new control, generated automatically if no ID is given + * @param {object} [mSettings] Initial settings for the new control + * + * @class + * + * @author SAP SE + * @version ${version} + * + * @constructor + * @extends sap.ui.core.Control + * @public + * @since 1.22 + * @alias library.j.SubControl + */ + (Control) => + Control.extend(`library.j.SubControl`, { + metadata: { + properties: { + /** + * MyProp property + * @since 1.46 + */ + MyProp: { + type: "boolean", + group: `Misc`, + defaultValue: false, + }, + }, + }, + }) + ); diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/ValidPropertyDefaultValue.js b/packages/builder/test/fixtures/library.j/main/src/library/j/ValidPropertyDefaultValue.js new file mode 100644 index 00000000000..61dbf44dbd5 --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/ValidPropertyDefaultValue.js @@ -0,0 +1,184 @@ +/*! + * ${copyright} + */ +sap.ui.define( + [ + "sap/ui/core/Control", + "./library", + "./core/library", + "sap/external/thirdparty/library", + "sap/external2/thirdparty/library", + "sap/external3/thirdparty/library", + ], + ( + Control, + { MyValidEnum, ThisIsEnumToo: RenamedEnum }, + coreLibrary, + [ {arrPattern}, {arrWith: {deep: arrPatternDeepDestruct}}], + { objPattern: {deeply: {destructured: objPatternDeepDestruct}, objPattern1Lvl} }, + libraryExt + ) => { + const { AnotherValidEnum } = coreLibrary; + const { Buzz } = AnotherValidEnum; + const { AnotherValidEnum: {Buzz: BuzzRenamed} } = coreLibrary; + const { AnotherValidEnum: AnotherRenamedEnum } = coreLibrary; + const { H1 } = sap.ui.core.TitleLevel; + const { Value2: RenamedValue2 } = RenamedEnum; + const [ {arrPatternVarDef}, {nested: {arrPatternVarDef: arrPatternVarDefNestedAndRenamed}} ] = libraryExt; + + /** + * @class + * My super documentation of this class + * + * @extends sap.ui.core.Control + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.ValidPropertyDefaultValue + * @ui5-metamodel text + */ + var ValidPropertyDefaultValue = Control.extend( + "library.j.ValidPropertyDefaultValue", + { + metadata: { + properties: { + /** + * validPropertyDefaultValueEnumSimpleDestructuring + */ + validPropertyDefaultValueEnumSimpleDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: AnotherValidEnum.Buzz + }, + + /** + * validPropertyDefaultValueEnumChainedDestructuring + */ + validPropertyDefaultValueEnumChainedDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: Buzz + }, + + /** + * validPropertyDefaultValueEnumNestedDestructuring + */ + validPropertyDefaultValueEnumNestedDestructuring: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: BuzzRenamed + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRename + */ + validPropertyDefaultValueEnumDestructuringWithRename: { + type: "library.j.core.AnotherValidEnum", + group: "Misc", + defaultValue: AnotherRenamedEnum.Fizz + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRenameInArguments + */ + validPropertyDefaultValueEnumDestructuringWithRenameInArguments: + { + type: "library.j.ThisIsEnumToo", + group: "Misc", + defaultValue: RenamedEnum.Value1 + }, + + /** + * validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar + */ + validPropertyDefaultValueEnumDestructuringWithRenameInArgumentsAndLocalVar: + { + type: "library.j.ThisIsEnumToo", + group: "Misc", + defaultValue: RenamedValue2 + }, + + /** + * validPropertyDefaultValueEnumViaDestructuringInArrowFn + */ + validPropertyDefaultValueEnumViaDestructuringInArrowFn: + { + type: "library.j.MyValidEnum", + group: "Misc", + defaultValue: MyValidEnum.Foo + }, + + /** + * validPropertyDefaultValueEnumViaDestructuringGlobal + */ + validPropertyDefaultValueEnumViaDestructuringGlobal: { + type: "sap.ui.core.TitleLevel", + group: "Misc", + defaultValue: H1 + }, + + /** + * validPropertyDefaultValueArrPattern + */ + validPropertyDefaultValueArrPattern: { + type: "sap.external.thirdparty.0", + group: "Misc", + defaultValue: arrPattern + }, + + /** + * validPropertyDefaultValueArrPatternDeepDestruct + */ + validPropertyDefaultValueArrPatternDeepDestruct: { + type: "sap.external.thirdparty.1.arrWith", + group: "Misc", + defaultValue: arrPatternDeepDestruct + }, + + /** + * validPropertyDefaultValueArrPatternDeepDestruct + */ + validPropertyDefaultValueObjPatternDeepDestruct: { + type: "sap.external2.thirdparty.objPattern.deeply", + group: "Misc", + defaultValue: objPatternDeepDestruct + }, + + /** + * validPropertyDefaultValueObjPatternNested + */ + validPropertyDefaultValueObjPatternNested: { + type: "sap.external2.thirdparty.objPattern", + group: "Misc", + defaultValue: objPattern1Lvl + }, + + /** + * validPropertyDefaultValueArrPatternVarDef + */ + validPropertyDefaultValueArrPatternVarDef: { + type: "sap.external3.thirdparty.0", + group: "Misc", + defaultValue: arrPatternVarDef + }, + + /** + * validPropertyDefaultValueArrPatternVarDef + */ + validPropertyDefaultValueArrPatternVarDefNestedAndRenamed: { + type: "sap.external3.thirdparty.1.nested", + group: "Misc", + defaultValue: arrPatternVarDefNestedAndRenamed + } + }, + }, + renderer: function () {}, + } + ); + + return ValidPropertyDefaultValue; + }, + /* bExport= */ true +); diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/core/library.js b/packages/builder/test/fixtures/library.j/main/src/library/j/core/library.js new file mode 100644 index 00000000000..ba22deece27 --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/core/library.js @@ -0,0 +1,34 @@ +/*! + * ${copyright} + */ +sap.ui.define([], function () { + sap.ui.getCore().initLibrary({ + name: "library.j.core", + version: "${version}", + dependencies: ["sap.ui.core"], + designtime: "library/j/core/designtime/library.designtime", + types: ["library.j.core.AnotherValidEnum"], + }); + + /** + * AnotherValidEnum + * + * @enum {string} + * @public + * @ui5-metamodel This enumeration also will be described in the UI5 (legacy) designtime metamodel + */ + library.j.core.AnotherValidEnum = { + /** + * Fizz + * @public + */ + Fizz: "Fizz", + /** + * Buzz + * @public + */ + Buzz: "Buzz", + }; + + return library.j.core; +}); diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/library.js b/packages/builder/test/fixtures/library.j/main/src/library/j/library.js new file mode 100644 index 00000000000..8a5d5343e0c --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/library.js @@ -0,0 +1,53 @@ +/*! + * ${copyright} + */ +sap.ui.define([], function () { + sap.ui.getCore().initLibrary({ + name: "library.j", + version: "${version}", + dependencies: ["sap.ui.core"], + designtime: "library/j/designtime/library.designtime", + types: ["library.j.MyValidEnum"], + }); + + /** + * MyValidEnum + * + * @enum {string} + * @public + * @ui5-metamodel This enumeration also will be described in the UI5 (legacy) designtime metamodel + */ + library.j.MyValidEnum = { + /** + * Foo + * @public + */ + Foo: "Foo", + /** + * Bar + * @public + */ + Bar: "Bar", + }; + + /** + * ThisIsEnumToo + * + * @enum {string} + * @public + */ + library.j.ThisIsEnumToo = { + /** + * Value1 + * @public + */ + Value1: "Value1", + /** + * Value2 + * @public + */ + Value2: "Value2", + }; + + return library.j; +}); diff --git a/packages/builder/test/fixtures/library.j/main/src/library/j/someGenerator.js b/packages/builder/test/fixtures/library.j/main/src/library/j/someGenerator.js new file mode 100644 index 00000000000..3f059af2c6f --- /dev/null +++ b/packages/builder/test/fixtures/library.j/main/src/library/j/someGenerator.js @@ -0,0 +1,12 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - Generators + * - YeldExpression + */ +sap.ui.define([], function* someGenerator(genVar) { + yield genVar++; +}); diff --git a/packages/builder/test/fixtures/library.k/package.json b/packages/builder/test/fixtures/library.k/package.json new file mode 100644 index 00000000000..5961b3664bb --- /dev/null +++ b/packages/builder/test/fixtures/library.k/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.j", + "version": "1.0.0", + "description": "Simple SAPUI5 based library for testing JSDoc builds", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.k/src/library/k/.library b/packages/builder/test/fixtures/library.k/src/library/k/.library new file mode 100644 index 00000000000..641376edc4e --- /dev/null +++ b/packages/builder/test/fixtures/library.k/src/library/k/.library @@ -0,0 +1,12 @@ + + + + library.k + SAP SE + Some fancy copyright + 1.0.0 + + {{title}} + {{description}} + + diff --git a/packages/builder/test/fixtures/library.k/src/library/k/library.js b/packages/builder/test/fixtures/library.k/src/library/k/library.js new file mode 100644 index 00000000000..b58243b6a15 --- /dev/null +++ b/packages/builder/test/fixtures/library.k/src/library/k/library.js @@ -0,0 +1,30 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], function(Core) { + + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.k", + version: "1.0.0", + dependencies : [], + types: [ + "library.k.AnyType" + ], + interfaces: [ + "library.k.IAny" + ], + controls: [ + "library.k.AnyControl" + ], + elements: [ + "library.k.AnyElement" + ], + }); + + return thisLib; + +}); diff --git a/packages/builder/test/fixtures/library.k/src/library/k/messagebundle.properties b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_de.properties b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_de.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_de.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_en.properties b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_en.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.k/src/library/k/messagebundle_en.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.k/ui5.yaml b/packages/builder/test/fixtures/library.k/ui5.yaml new file mode 100644 index 00000000000..eb48ff28766 --- /dev/null +++ b/packages/builder/test/fixtures/library.k/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.k +resources: + configuration: + paths: + src: src + test: test diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/.library b/packages/builder/test/fixtures/library.l/main/src/library/l/.library new file mode 100644 index 00000000000..eb743173b9e --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/.library @@ -0,0 +1,11 @@ + + + + library.l + SAP SE + ${copyright} + ${version} + + Library L + + diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/some.js b/packages/builder/test/fixtures/library.l/main/src/library/l/some.js new file mode 100644 index 00000000000..10f9fd2dc29 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/some.js @@ -0,0 +1,16 @@ +/*! + * ${copyright} + */ + +sap.ui.define([], + function() { + "use strict"; + + /** + * @alias library.l + * @namespace + * @public + */ + var SomeFunction = function() {}; + +}, /* bExport= */ true); diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/index.js b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/index.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/index.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File0.js b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File1.js b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/subdir/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File0.js b/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File0.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File0.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File1.js b/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File1.js new file mode 100644 index 00000000000..1fd63bd73d5 --- /dev/null +++ b/packages/builder/test/fixtures/library.l/main/src/library/l/thirdparty/File1.js @@ -0,0 +1,5 @@ +// hello world +function hello(name) { + console.log("hello " + name); +} +hello("world"); diff --git a/packages/builder/test/fixtures/library.m/package.json b/packages/builder/test/fixtures/library.m/package.json new file mode 100644 index 00000000000..289b1430a7b --- /dev/null +++ b/packages/builder/test/fixtures/library.m/package.json @@ -0,0 +1,8 @@ +{ + "name": "library.m", + "version": "1.0.0", + "description": "Simple SAPUI5 based library - test for library formatter (with manifest.json)", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.m/src/library/m/.library b/packages/builder/test/fixtures/library.m/src/library/m/.library new file mode 100644 index 00000000000..3b4c573fae1 --- /dev/null +++ b/packages/builder/test/fixtures/library.m/src/library/m/.library @@ -0,0 +1,11 @@ + + + + library.m + SAP SE + ${copyright} + ${version} + + Library M + + diff --git a/packages/builder/test/fixtures/library.m/src/library/m/some.js b/packages/builder/test/fixtures/library.m/src/library/m/some.js new file mode 100644 index 00000000000..81e73436075 --- /dev/null +++ b/packages/builder/test/fixtures/library.m/src/library/m/some.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.m/test/library/m/Test.html b/packages/builder/test/fixtures/library.m/test/library/m/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/library.m/ui5.yaml b/packages/builder/test/fixtures/library.m/ui5.yaml new file mode 100644 index 00000000000..64890566945 --- /dev/null +++ b/packages/builder/test/fixtures/library.m/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.m diff --git a/packages/builder/test/fixtures/library.n/package.json b/packages/builder/test/fixtures/library.n/package.json new file mode 100644 index 00000000000..79667045cb1 --- /dev/null +++ b/packages/builder/test/fixtures/library.n/package.json @@ -0,0 +1,8 @@ +{ + "name": "library.n", + "version": "1.0.0", + "description": "Simple SAPUI5 based library - test for disabled string bundling", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.n/src/library/n/.library b/packages/builder/test/fixtures/library.n/src/library/n/.library new file mode 100644 index 00000000000..b11839e0ea7 --- /dev/null +++ b/packages/builder/test/fixtures/library.n/src/library/n/.library @@ -0,0 +1,11 @@ + + + + library.n + SAP SE + ${copyright} + ${version} + + Library N + + diff --git a/packages/builder/test/fixtures/library.n/src/library/n/MyModuleRequiringGlobalScope.js b/packages/builder/test/fixtures/library.n/src/library/n/MyModuleRequiringGlobalScope.js new file mode 100644 index 00000000000..387a331cf1c --- /dev/null +++ b/packages/builder/test/fixtures/library.n/src/library/n/MyModuleRequiringGlobalScope.js @@ -0,0 +1 @@ +const magic = {text: "It's magic!"}; diff --git a/packages/builder/test/fixtures/library.n/src/library/n/Some.js b/packages/builder/test/fixtures/library.n/src/library/n/Some.js new file mode 100644 index 00000000000..c99ebbe708a --- /dev/null +++ b/packages/builder/test/fixtures/library.n/src/library/n/Some.js @@ -0,0 +1,7 @@ +/*! + * ${copyright} + */ +sap.ui.define([], () => { + const some = {text: "Just a module."}; + return some; +}) diff --git a/packages/builder/test/fixtures/library.n/test/library/n/Test.html b/packages/builder/test/fixtures/library.n/test/library/n/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/library.o/package.json b/packages/builder/test/fixtures/library.o/package.json new file mode 100644 index 00000000000..384c8b0fa42 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.o", + "version": "1.0.0", + "description": "Simple SAPUI5 based library with Terminologies", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/builder/test/fixtures/library.o/src/library/o/.library b/packages/builder/test/fixtures/library.o/src/library/o/.library new file mode 100644 index 00000000000..7214eea682b --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/.library @@ -0,0 +1,12 @@ + + + + library.o + SAP SE + ${copyright} + ${version} + + {{title}} + {{description}} + + diff --git a/packages/builder/test/fixtures/library.o/src/library/o/library.js b/packages/builder/test/fixtures/library.o/src/library/o/library.js new file mode 100644 index 00000000000..d8fcee8a38c --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/library.js @@ -0,0 +1,17 @@ +/*! + * Some fancy copyright + */ +sap.ui.define([ + 'sap/ui/core/Core', +], (Core) => { + "use strict"; + + sap.ui.getCore().initLibrary({ + name : "library.o", + version: "1.0.0", + dependencies : [] + }); + + return thisLib; + +}); diff --git a/packages/builder/test/fixtures/library.o/src/library/o/manifest.json b/packages/builder/test/fixtures/library.o/src/library/o/manifest.json new file mode 100644 index 00000000000..f2200523f22 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/manifest.json @@ -0,0 +1,65 @@ +{ + "_version": "1.58.0", + "sap.app": { + "id": "library.o", + "type": "library", + "embeds": [], + "applicationVersion": { + "version": "1.0.0" + }, + "title": "{{title}}", + "description": "{{description}}", + "resources": "resources.json", + "offline": true + }, + "sap.ui": { + "technology": "UI5", + "supportedThemes": [] + }, + "sap.ui5": { + "dependencies": { + "libs": {} + }, + "library": { + "i18n": { + "bundleUrl": "messagebundle.properties", + "terminologies": { + "sports": { + "bundleUrl": "sports.properties", + "bundleUrlRelativeTo": "manifest" + }, + "travel": { + "bundleUrl": "travel.properties", + "bundleUrlRelativeTo": "manifest" + } + }, + "enhanceWith": [ + { + "bundleUrl": "myfolder1/i18n.properties", + "bundleUrlRelativeTo": "manifest", + "terminologies": { + "sports": { + "bundleUrl": "myfolder1/soccer.properties", + "bundleUrlRelativeTo": "manifest" + }, + "travel": { + "bundleUrl": "myfolder1/vehicles.properties", + "bundleUrlRelativeTo": "manifest" + } + } + }, + { + "bundleUrl": "myfolder2/i18n.properties", + "bundleUrlRelativeTo": "manifest", + "terminologies": { + "travel": { + "bundleUrl": "myfolder2/bicycles.properties", + "bundleUrlRelativeTo": "manifest" + } + } + } + ] + } + } + } +} diff --git a/packages/builder/test/fixtures/library.o/src/library/o/messagebundle.properties b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_de.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_de.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_en.properties new file mode 100644 index 00000000000..b23c5ecd10e --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/messagebundle_en.properties @@ -0,0 +1,2 @@ +title=a title +description=a description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n.properties new file mode 100644 index 00000000000..7a2a6e8bb55 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n.properties @@ -0,0 +1 @@ +title=my company 1 title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n_en.properties new file mode 100644 index 00000000000..7a2a6e8bb55 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/i18n_en.properties @@ -0,0 +1 @@ +title=my company 1 title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer.properties new file mode 100644 index 00000000000..5ca5f663bcc --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer.properties @@ -0,0 +1 @@ +title=my soccer title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_de.properties new file mode 100644 index 00000000000..1c9b39ba50c --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_de.properties @@ -0,0 +1 @@ +title=Mein Fussball Titel \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_en.properties new file mode 100644 index 00000000000..5ca5f663bcc --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/soccer_en.properties @@ -0,0 +1 @@ +title=my soccer title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles.properties new file mode 100644 index 00000000000..2e42e0f6f03 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles.properties @@ -0,0 +1 @@ +title=my vehicle title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_de.properties new file mode 100644 index 00000000000..5cc4cab70cd --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_de.properties @@ -0,0 +1 @@ +title=Mein Fahrzeugstitel \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_en.properties new file mode 100644 index 00000000000..2e42e0f6f03 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder1/vehicles_en.properties @@ -0,0 +1 @@ +title=my vehicle title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles.properties new file mode 100644 index 00000000000..b288e4bfd1e --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles.properties @@ -0,0 +1 @@ +title=my bicycle title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_de.properties new file mode 100644 index 00000000000..f613ffa9d49 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_de.properties @@ -0,0 +1 @@ +title=Mein Fahrradtitel \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_en.properties new file mode 100644 index 00000000000..b288e4bfd1e --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/bicycles_en.properties @@ -0,0 +1 @@ +title=my bicycle title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n.properties new file mode 100644 index 00000000000..37564182772 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n.properties @@ -0,0 +1 @@ +title=my company 2 title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n_en.properties new file mode 100644 index 00000000000..37564182772 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/myfolder2/i18n_en.properties @@ -0,0 +1 @@ +title=my company 2 title \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/sports.properties b/packages/builder/test/fixtures/library.o/src/library/o/sports.properties new file mode 100644 index 00000000000..4b56228752f --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/sports.properties @@ -0,0 +1,2 @@ +title=a sports title +description=a sports description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/sports_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/sports_de.properties new file mode 100644 index 00000000000..c6ce6e0ffa2 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/sports_de.properties @@ -0,0 +1,2 @@ +title=ein Sporttitel +description=eine Sportbeschreibung \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/sports_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/sports_en.properties new file mode 100644 index 00000000000..4b56228752f --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/sports_en.properties @@ -0,0 +1,2 @@ +title=a sports title +description=a sports description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/travel.properties b/packages/builder/test/fixtures/library.o/src/library/o/travel.properties new file mode 100644 index 00000000000..1e5ad188e03 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/travel.properties @@ -0,0 +1,2 @@ +title=a travel title +description=a travel description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/travel_de.properties b/packages/builder/test/fixtures/library.o/src/library/o/travel_de.properties new file mode 100644 index 00000000000..67689b27fc0 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/travel_de.properties @@ -0,0 +1,2 @@ +title=ein Reisetitel +description=eine Reisebeschreibung \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/src/library/o/travel_en.properties b/packages/builder/test/fixtures/library.o/src/library/o/travel_en.properties new file mode 100644 index 00000000000..1e5ad188e03 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/src/library/o/travel_en.properties @@ -0,0 +1,2 @@ +title=a travel title +description=a travel description \ No newline at end of file diff --git a/packages/builder/test/fixtures/library.o/ui5.yaml b/packages/builder/test/fixtures/library.o/ui5.yaml new file mode 100644 index 00000000000..ac1f5d168c0 --- /dev/null +++ b/packages/builder/test/fixtures/library.o/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.o diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/.library" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/.library" new file mode 100644 index 00000000000..107e40e06e7 --- /dev/null +++ "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/.library" @@ -0,0 +1,11 @@ + + + + library.ø + SAP SE + Some fancy copyright + ${version} + + Library Ø + + diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/rules/MyControl.support.js" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/rules/MyControl.support.js" new file mode 100644 index 00000000000..105f08ef0d0 --- /dev/null +++ "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/rules/MyControl.support.js" @@ -0,0 +1,36 @@ +/*! + * ${copyright} + */ +/** + * Defines support rules + */ + sap.ui.define(['sap/ui/support/library', 'sap/base/Log'], + function(SupportLib, Log) { + 'use strict'; + + //********************************************************** + // Rule Definitions + //********************************************************** + + var oRule = { + id: "oRule", + audiences: [Audiences.Application], + categories: [Categories.Usage], + enabled: true, + minversion: '1.71', + title: 'Title', + description: 'description', + resolution: 'resolution', + check: function(oIssueManager, oCoreFacade, oScope) { + oIssueManager.addIssue({ + severity: Severity.High, + details: 'Looking good today!' + }); + } + }; + + return [ + oRule + ]; + + }, true); diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/some.js" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/some.js" new file mode 100644 index 00000000000..fa1d786f73c --- /dev/null +++ "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/some.js" @@ -0,0 +1,7 @@ +/*! + * ${copyright} + */ +(function() { + var someNonUglifiedVariable = "World"; + console.log('Hello ' + someNonUglifiedVariable); +})(); diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/Button.less" @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\303\237rc/library/\303\270/themes/\321\206\320\262\320\265\321\202\321\217/library.source.less" @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git "a/packages/builder/test/fixtures/library.\303\270/m\303\241in/\345\220\211/library/\303\270/Test.html" "b/packages/builder/test/fixtures/library.\303\270/m\303\241in/\345\220\211/library/\303\270/Test.html" new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/module.a/dev/devTools.js b/packages/builder/test/fixtures/module.a/dev/devTools.js new file mode 100644 index 00000000000..e035bfaeab6 --- /dev/null +++ b/packages/builder/test/fixtures/module.a/dev/devTools.js @@ -0,0 +1 @@ +console.log("dev dev dev"); diff --git a/packages/builder/test/fixtures/module.a/dist/index.js b/packages/builder/test/fixtures/module.a/dist/index.js new file mode 100644 index 00000000000..019c0f4bc8e --- /dev/null +++ b/packages/builder/test/fixtures/module.a/dist/index.js @@ -0,0 +1 @@ +console.log("Hello World!"); diff --git a/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap-ui-core.js b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap-ui-core.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/Global.js b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/Global.js new file mode 100644 index 00000000000..3e9fd03460a --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/Global.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('${buildtime}'); diff --git a/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/.library b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/.library new file mode 100644 index 00000000000..dd41ad2dd60 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/.library @@ -0,0 +1,20 @@ + + + + sap.ui.core + SAP SE + ${copyright} + ${version} + + Core + + + + + + + + + + diff --git a/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Core.js b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Core.js new file mode 100644 index 00000000000..3e9fd03460a --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Core.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('${buildtime}'); diff --git a/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Icon.js b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Icon.js new file mode 100644 index 00000000000..3e9fd03460a --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-buildtime/main/src/sap/ui/core/Icon.js @@ -0,0 +1,5 @@ +/*! + * ${copyright} + */ +// replacement of $\{buildtime\} is only made in sap/ui/Global.js and Global-dbg.js as well as Core.js and Core-dbg.js +console.log('${buildtime}'); diff --git a/packages/builder/test/fixtures/sap.ui.core-evo/main/src/sap/ui/core/.library b/packages/builder/test/fixtures/sap.ui.core-evo/main/src/sap/ui/core/.library new file mode 100644 index 00000000000..134688b25fa --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-evo/main/src/sap/ui/core/.library @@ -0,0 +1,11 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + 1.0.0 + + Library sap.ui.core + + diff --git a/packages/builder/test/fixtures/sap.ui.core-evo/main/src/ui5loader.js b/packages/builder/test/fixtures/sap.ui.core-evo/main/src/ui5loader.js new file mode 100644 index 00000000000..0e76321fdef --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core-evo/main/src/ui5loader.js @@ -0,0 +1 @@ +// this is just a marker file to enable the EVO bundle format diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/sap-ui-core.js b/packages/builder/test/fixtures/sap.ui.core/main/src/sap-ui-core.js new file mode 100644 index 00000000000..bd9958b6ed8 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/sap-ui-core.js @@ -0,0 +1,4 @@ +(function () { + var thisShouldBeOverwritten = true; + console.log(thisShouldBeOverwritten); +})() diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/.library b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/.library new file mode 100644 index 00000000000..9089c4f9b38 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/.library @@ -0,0 +1,20 @@ + + + + sap.ui.core + SAP SE + Some fancy copyright + ${version} + + Core + + + + + + + + + + diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/Core.js b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/Core.js new file mode 100644 index 00000000000..66bd2c43f22 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/Core.js @@ -0,0 +1,4 @@ +(function () { + var core = true; + console.log(core); +})() diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/one.js b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/one.js new file mode 100644 index 00000000000..753bd052381 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/one.js @@ -0,0 +1,3 @@ +function One(){ + return 1; +} diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/some.js b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/some.js new file mode 100644 index 00000000000..f39184874b5 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/sap/ui/core/some.js @@ -0,0 +1,14 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); + +/* + * function add(c) - this is still not implemented. + * @private + * @param {Component} c + * @name add + * This comment should be removed in the preload bundle although it contains the magic + * sequence "opening parenthesis - letter c - closing parenthesis" which usually indicates + * a c o p y r i g h t comment. + */ diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader-autoconfig.js b/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader-autoconfig.js new file mode 100644 index 00000000000..783bce8ec31 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader-autoconfig.js @@ -0,0 +1,4 @@ +(function () { + var thisIsTheUi5LoaderAutoconfig = true; + console.log(thisIsTheUi5LoaderAutoconfig); +})() diff --git a/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader.js b/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader.js new file mode 100644 index 00000000000..5e44b1dd618 --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/main/src/ui5loader.js @@ -0,0 +1,4 @@ +(function () { + var thisIsTheUi5Loader = true; + console.log(thisIsTheUi5Loader); +})() diff --git a/packages/builder/test/fixtures/sap.ui.core/main/test/sap/ui/core/Test.html b/packages/builder/test/fixtures/sap.ui.core/main/test/sap/ui/core/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/fixtures/sap.ui.core/package.json b/packages/builder/test/fixtures/sap.ui.core/package.json new file mode 100644 index 00000000000..883ee434f8f --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/package.json @@ -0,0 +1,10 @@ +{ + "name": "sap.ui.core", + "version": "1.0.0", + "description": "Core library of SAPUI5", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} + diff --git a/packages/builder/test/fixtures/sap.ui.core/ui5.yaml b/packages/builder/test/fixtures/sap.ui.core/ui5.yaml new file mode 100644 index 00000000000..f85cc2d23ba --- /dev/null +++ b/packages/builder/test/fixtures/sap.ui.core/ui5.yaml @@ -0,0 +1,11 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: sap.ui.core +resources: + configuration: + paths: + src: main/src + test: main/test + diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/package.json b/packages/builder/test/fixtures/sourcemaps/test.application/package.json new file mode 100644 index 00000000000..39b05f03abb --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/package.json @@ -0,0 +1,6 @@ +{ + "name": "test..application", + "private": true, + "version": "1.0.0", + "description": "" +} diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/ui5.yaml b/packages/builder/test/fixtures/sourcemaps/test.application/ui5.yaml new file mode 100644 index 00000000000..1124b73a86c --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/ui5.yaml @@ -0,0 +1,8 @@ +--- +specVersion: "3.1" +type: application +metadata: + name: test.application + copyright: | + Test Application + Copyright ${currentYear} diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/webapp/JavaScriptSourceWithCopyrightPlaceholder.js b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/JavaScriptSourceWithCopyrightPlaceholder.js new file mode 100644 index 00000000000..aada6cb3734 --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/JavaScriptSourceWithCopyrightPlaceholder.js @@ -0,0 +1,11 @@ +/* + * ${copyright} + */ + +sap.ui.define((function() { + return { + functionWithinJavaScriptSourceWithCopyrightPlaceholder() { + functionCallWithinJavaScriptSourceWithCopyrightPlaceholder(); + } + } +})); diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js new file mode 100644 index 00000000000..669fb02b509 --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js @@ -0,0 +1,2 @@ +"use strict";sap.ui.define([],function(){"use strict";var i={functionWithinTypeScriptSource(){functionCallWithinTypeScriptSource()}};return i}); +//# sourceMappingURL=TypeScriptSource.js.map \ No newline at end of file diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js.map b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js.map new file mode 100644 index 00000000000..b146bc061b0 --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.js.map @@ -0,0 +1 @@ +{"version":3,"file":"TypeScriptSource.js","names":["functionWithinTypeScriptSource","functionCallWithinTypeScriptSource","__exports"],"sources":["TypeScriptSource.ts"],"sourcesContent":["export default {\n\tfunctionWithinTypeScriptSource() {\n\t\tfunctionCallWithinTypeScriptSource();\n\t}\n}\n"],"mappings":"4DAAe,CACdA,iCACCC,oCACD,GACA,OAAAC,CAAA"} \ No newline at end of file diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.ts b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.ts new file mode 100644 index 00000000000..589bfa49860 --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/TypeScriptSource.ts @@ -0,0 +1,5 @@ +export default { + functionWithinTypeScriptSource() { + functionCallWithinTypeScriptSource(); + } +} diff --git a/packages/builder/test/fixtures/sourcemaps/test.application/webapp/manifest.json b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/manifest.json new file mode 100644 index 00000000000..639d65e26a6 --- /dev/null +++ b/packages/builder/test/fixtures/sourcemaps/test.application/webapp/manifest.json @@ -0,0 +1,6 @@ +{ + "sap.app": { + "type": "application", + "id": "test.application" + } +} diff --git a/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/Button.less b/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/Button.less new file mode 100644 index 00000000000..ca968183f77 --- /dev/null +++ b/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/Button.less @@ -0,0 +1,3 @@ +.someClass { + color: @someColor +} diff --git a/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/library.source.less b/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/library.source.less new file mode 100644 index 00000000000..834de919e23 --- /dev/null +++ b/packages/builder/test/fixtures/theme.j/main/src/theme/j/themes/somefancytheme/library.source.less @@ -0,0 +1,2 @@ +@someColor: black; +@import "Button.less"; diff --git a/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme new file mode 100644 index 00000000000..4c62f26114c --- /dev/null +++ b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theme @@ -0,0 +1,9 @@ + + + + my_theme + me + ${copyright} + ${version} + + \ No newline at end of file diff --git a/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming new file mode 100644 index 00000000000..83b6c785a87 --- /dev/null +++ b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/.theming @@ -0,0 +1,27 @@ +{ + "sEntity": "Theme", + "sId": "sap_belize", + "oExtends": "base", + "sVendor": "SAP", + "aBundled": ["sap_belize_plus"], + "mCssScopes": { + "library": { + "sBaseFile": "library", + "sEmbeddingMethod": "APPEND", + "aScopes": [ + { + "sLabel": "Contrast", + "sSelector": "sapContrast", + "sEmbeddedFile": "sap_belize_plus.library", + "sEmbeddedCompareFile": "library", + "sThemeIdSuffix": "Contrast", + "sThemability": "PUBLIC", + "aThemabilityFilter": [ + "Color" + ], + "rExcludeSelector": "\\.sapContrastPlus\\W" + } + ] + } + } +} diff --git a/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less new file mode 100644 index 00000000000..d3286002bfe --- /dev/null +++ b/packages/builder/test/fixtures/theme.library.e/src/theme/library/e/themes/my_theme/library.source.less @@ -0,0 +1,9 @@ +/*! + * ${copyright} + */ + +@mycolor: blue; + +.sapUiBody { + background-color: @mycolor; +} diff --git a/packages/builder/test/fixtures/theme.library.e/test/theme/library/e/Test.html b/packages/builder/test/fixtures/theme.library.e/test/theme/library/e/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/builder/test/lib/builder/builder.js b/packages/builder/test/lib/builder/builder.js new file mode 100644 index 00000000000..464734f1077 --- /dev/null +++ b/packages/builder/test/lib/builder/builder.js @@ -0,0 +1,2234 @@ +import test from "ava"; +import path from "node:path"; +import {createRequire} from "node:module"; +import fs from "graceful-fs"; +import {promisify} from "node:util"; +const readFile = promisify(fs.readFile); +import {directoryDeepEqual, findFiles} from "../../utils/fshelper.js"; +import sinon from "sinon"; +import {graphFromObject, graphFromPackageDependencies} from "@ui5/project/graph"; +import * as taskRepository from "../../../lib/tasks/taskRepository.js"; +import {setLogLevel} from "@ui5/logger"; + +// Using CommonsJS require since JSON module imports are still experimental +const require = createRequire(import.meta.url); + +const __dirname = import.meta.dirname; + +const applicationAPath = path.join(__dirname, "..", "..", "fixtures", "application.a"); +const applicationGPath = path.join(__dirname, "..", "..", "fixtures", "application.g"); +const applicationHPath = path.join(__dirname, "..", "..", "fixtures", "application.h"); +const applicationIPath = path.join(__dirname, "..", "..", "fixtures", "application.i"); +const applicationJPath = path.join(__dirname, "..", "..", "fixtures", "application.j"); +const applicationKPath = path.join(__dirname, "..", "..", "fixtures", "application.k"); +const applicationLPath = path.join(__dirname, "..", "..", "fixtures", "application.l"); +const applicationMPath = path.join(__dirname, "..", "..", "fixtures", "application.m"); +const applicationØPath = path.join(__dirname, "..", "..", "fixtures", "application.ø"); +const applicationOPath = path.join(__dirname, "..", "..", "fixtures", "application.o"); +const collectionPath = path.join(__dirname, "..", "..", "fixtures", "collection"); +const libraryDPath = path.join(__dirname, "..", "..", "fixtures", "library.d"); +const libraryEPath = path.join(__dirname, "..", "..", "fixtures", "library.e"); +const libraryHPath = path.join(__dirname, "..", "..", "fixtures", "library.h"); +const libraryIPath = path.join(__dirname, "..", "..", "fixtures", "library.i"); +const libraryJPath = path.join(__dirname, "..", "..", "fixtures", "library.j"); +const libraryLPath = path.join(__dirname, "..", "..", "fixtures", "library.l"); +const libraryØPath = path.join(__dirname, "..", "..", "fixtures", "library.ø"); +const libraryOPath = path.join(__dirname, "..", "..", "fixtures", "library.o"); +const libraryCore = path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo"); +const libraryCoreBuildtime = path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-buildtime"); +const themeJPath = path.join(__dirname, "..", "..", "fixtures", "theme.j"); +const themeLibraryEPath = path.join(__dirname, "..", "..", "fixtures", "theme.library.e"); +const newLineRegexp = /\r?\n|\r/g; + +function clone(obj) { + return JSON.parse(JSON.stringify(obj)); +} + +function cloneProjectTree(tree) { + tree = clone(tree); + + function increaseDepth(node) { + node._level++; + if (Array.isArray(node.dependencies)) { + node.dependencies.forEach(increaseDepth); + } + } + + increaseDepth(tree); + return tree; +} + +async function checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath) { + for (let i = 0; i < expectedFiles.length; i++) { + const expectedFile = expectedFiles[i]; + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + const currentFileContentPromise = readFile(destFile, "utf8"); + const expectedFileContentPromise = readFile(expectedFile, "utf8"); + const assertContents = ([currentContent, expectedContent]) => { + if (expectedFile.endsWith("sap-ui-cachebuster-info.json")) { + currentContent = JSON.parse(currentContent.replace(/(:\s+)(\d+)/g, ": 0")); + expectedContent = JSON.parse(expectedContent.replace(/(:\s+)(\d+)/g, ": 0")); + t.deepEqual(currentContent, expectedContent); + } else { + if (expectedFile.endsWith(".json")) { + try { + t.deepEqual(JSON.parse(currentContent), JSON.parse(expectedContent), expectedFile); + } catch (e) { + t.falsy(e, expectedFile); + } + } + t.is(currentContent.replace(newLineRegexp, "\n"), + expectedContent.replace(newLineRegexp, "\n"), + relativeFile); + } + }; + await Promise.all([currentFileContentPromise, expectedFileContentPromise]).then(assertContents); + } +} + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("Build application.a", async (t) => { + const destPath = "./test/tmp/build/application.a/dest"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest"); + + const graph = await graphFromPackageDependencies({ + cwd: applicationAPath + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateComponentPreload", "generateStandaloneAppBundle", "generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.a with dependencies", async (t) => { + const destPath = "./test/tmp/build/application.a/dest-deps"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest-deps"); + + const graph = await graphFromObject({ + dependencyTree: applicationATree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: [ + "generateComponentPreload", "generateStandaloneAppBundle", "generateVersionInfo", + "generateLibraryPreload", "escapeNonAsciiCharacters", "generateLibraryManifest" + ], + includedDependencies: ["*"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.a with dependencies exclude", async (t) => { + const destPath = "./test/tmp/build/application.a/dest-deps-excl"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest-deps-excl"); + + const graph = await graphFromObject({ + dependencyTree: applicationATree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: [ + "generateComponentPreload", "generateStandaloneAppBundle", "generateVersionInfo", + "generateLibraryPreload", "escapeNonAsciiCharacters", "generateLibraryManifest" + ], + includedDependencies: ["*"], + excludedDependencies: ["library.d"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.a self-contained", async (t) => { + const destPath = "./test/tmp/build/application.a/dest-self"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest-self"); + + const graph = await graphFromObject({ + dependencyTree: applicationATree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateComponentPreload", "generateVersionInfo"], + selfContained: true + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.a with dependencies self-contained", async (t) => { + const destPath = "./test/tmp/build/application.a/dest-depself"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest-depself"); + + const graph = await graphFromObject({ + dependencyTree: applicationATree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: [ + "generateComponentPreload", "generateVersionInfo", "escapeNonAsciiCharacters", + "generateLibraryManifest" + ], + includedDependencies: ["*"], + selfContained: true + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.a and clean target path", async (t) => { + const destPath = "./test/tmp/build/application.a/dest-clean"; + const destPathRubbishSubFolder = destPath + "/rubbish-should-be-deleted"; + const expectedPath = path.join("test", "expected", "build", "application.a", "dest-clean"); + + const graph1 = await graphFromObject({ + dependencyTree: applicationATree + }); + const graph2 = await graphFromObject({ + dependencyTree: applicationATree + }); + graph1.setTaskRepository(taskRepository); + await graph1.build({ + graph: graph1, + destPath: destPathRubbishSubFolder, + excludedTasks: ["*"] + }); + graph2.setTaskRepository(taskRepository); + await graph2.build({ + graph: graph2, + destPath, + cleanDest: true, + excludedTasks: ["*"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.g", async (t) => { + const destPath = "./test/tmp/build/application.g/dest"; + const expectedPath = path.join("test", "expected", "build", "application.g", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationGTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.g with component preload paths", async (t) => { + const destPath = "./test/tmp/build/application.g/dest2"; + const expectedPath = path.join("test", "expected", "build", "application.g", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationGTreeComponentPreloadPaths + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.g with excludes", async (t) => { + const destPath = "./test/tmp/build/application.g/excludes"; + const expectedPath = path.join("test", "expected", "build", "application.g", "excludes"); + + const graph = await graphFromObject({ + dependencyTree: applicationGTreeWithExcludes + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["*"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.h", async (t) => { + const destPath = "./test/tmp/build/application.h/dest"; + const expectedPath = path.join("test", "expected", "build", "application.h", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateComponentPreload", + "generateStandaloneAppBundle", "generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.h (no minify)", async (t) => { + const destPath = "./test/tmp/build/application.h/no-minify"; + const expectedPath = path.join("test", "expected", "build", "application.h", "no-minify"); + + const graph = await graphFromObject({ + dependencyTree: applicationHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["minify", "generateComponentPreload", + "generateStandaloneAppBundle", "generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.i", async (t) => { + const destPath = "./test/tmp/build/application.i/dest"; + const expectedPath = path.join("test", "expected", "build", "application.i", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationITree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.j", async (t) => { + const destPath = "./test/tmp/build/application.j/dest"; + const expectedPath = path.join("test", "expected", "build", "application.j", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.j with resources.json and version info", async (t) => { + const destPath = "./test/tmp/build/application.j/dest-resources-json"; + const expectedPath = path.join("test", "expected", "build", "application.j", "dest-resources-json"); + + sinon.stub(Date.prototype, "getFullYear").returns(2020); + sinon.stub(Date.prototype, "getMonth").returns(7); + sinon.stub(Date.prototype, "getDate").returns(12); + sinon.stub(Date.prototype, "getHours").returns(9); + sinon.stub(Date.prototype, "getMinutes").returns(17); + + const graph = await graphFromObject({ + dependencyTree: applicationJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + includedTasks: ["generateResourcesJson", "generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.k (componentPreload excludes)", async (t) => { + const destPath = "./test/tmp/build/application.k/dest"; + const expectedPath = path.join("test", "expected", "build", "application.k", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationKTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + includedTasks: ["generateComponentPreload"], + excludedTasks: ["*"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.k (package sub-components / componentPreload excludes)", async (t) => { + const destPath = "./test/tmp/build/application.k/dest-package-subcomponents"; + const expectedPath = path.join("test", "expected", "build", "application.k", "dest-package-subcomponents"); + + const graph = await graphFromObject({ + dependencyTree: applicationKPackageSubcomponentsTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + includedTasks: ["generateComponentPreload"], + excludedTasks: ["*"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.l: minification excludes, w/ namespace", async (t) => { + const destPath = "./test/tmp/build/application.l/dest"; + const expectedPath = path.join("test", "expected", "build", "application.l", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationLTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateComponentPreload", "generateStandaloneAppBundle", "generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.m: bundle should not contain hashbang but an empty line", async (t) => { + const destPath = "./test/tmp/build/application.m/dest"; + const expectedPath = path.join("test", "expected", "build", "application.m", "dest"); + + const graph = await graphFromPackageDependencies({ + cwd: applicationMPath + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.ø", async (t) => { + const destPath = "./test/tmp/build/application.ø/dest"; + const expectedPath = path.join("test", "expected", "build", "application.ø", "dest"); + + const graph = await graphFromObject({ + dependencyTree: applicationØTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateVersionInfo"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.d with copyright from .library file", async (t) => { + const destPath = "./test/tmp/build/library.d/dest"; + const expectedPath = path.join("test", "expected", "build", "library.d", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryDTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.e with copyright from metadata configuration of tree", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.e", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.e", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryETree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.e with build manifest", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.e", "build-manifest"); + const expectedPath = path.join("test", "expected", "build", "library.e", "build-manifest"); + const resultBuildManifestPath = path.join(__dirname, + "..", "..", "tmp", "build", "library.e", "build-manifest", ".ui5", "build-manifest.json"); + + // Stub date because of timestamp in build-manifest.json + const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); + const graph = await graphFromObject({ + dependencyTree: libraryETree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + createBuildManifest: true + }); + toISOStringStub.restore(); + + let expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Filter out build-manifest.json for manual comparison + expectedFiles = expectedFiles.filter((filePath) => { + return !filePath.endsWith("build-manifest.json"); + }); + + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + + const manifest = require(resultBuildManifestPath); + + t.deepEqual(manifest.project, { + "metadata": { + "name": "library.e" + }, + "resources": { + "configuration": { + "paths": { + "src": "resources", + "test": "test-resources" + } + } + }, + "specVersion": "2.6", + "type": "library" + }, "Build manifest contains expected project configuration"); + + t.deepEqual(manifest.buildManifest.tags, { + "/resources/library/e/library-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/e/library.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/e/library.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/e/some-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/e/some.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/e/some.js.map": { + "ui5:HasDebugVariant": true + } + }, "Build manifest contains expected tags"); + + t.is(manifest.buildManifest.namespace, "library/e", + "Build manifest contains expected namespace"); + + t.is(manifest.buildManifest.timestamp, "2022-07-27T09:00:00.000Z", + "Build manifest contains expected timestamp"); + + t.is(manifest.buildManifest.version, "1.0.0", + "Build manifest contains expected version"); +}); + +test.serial("Build library.h with custom bundles and component-preloads", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.h", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.h", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.h with custom bundles and component-preloads (no minify)", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.h", "no-minify"); + const expectedPath = path.join("test", "expected", "build", "library.h", "no-minify"); + + const graph = await graphFromObject({ + dependencyTree: libraryHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["minify", "generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.h w/ custom bundles, component-preloads, resources.json and build manifest", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.h", "dest-resources-json"); + const expectedPath = path.join("test", "expected", "build", "library.h", "dest-resources-json"); + const resultBuildManifestPath = path.join(__dirname, + "..", "..", "tmp", "build", "library.h", "dest-resources-json", ".ui5", "build-manifest.json"); + + // Stub date because of timestamp in build-manifest.json + const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); + const graph = await graphFromObject({ + dependencyTree: libraryHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + createBuildManifest: true, + includedTasks: ["generateResourcesJson"], + excludedTasks: ["generateLibraryPreload"] + }); + toISOStringStub.restore(); + + let expectedFiles = await findFiles(expectedPath); + // Filter out build-manifest.json for manual comparison + expectedFiles = expectedFiles.filter((filePath) => { + return !filePath.endsWith("build-manifest.json"); + }); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + + + const manifest = require(resultBuildManifestPath); + + t.deepEqual(manifest.project, { + "metadata": { + "name": "library.h" + }, + "resources": { + "configuration": { + "paths": { + "src": "resources", + "test": "test-resources" + } + } + }, + "specVersion": "2.6", + "type": "library" + }, "Build manifest contains expected project configuration"); + + t.deepEqual(manifest.buildManifest.tags, { + "/resources/library/h/components/Component-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/components/Component.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/Component.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/TodoComponent-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/components/TodoComponent.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/TodoComponent.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent1/Component-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/components/subcomponent1/Component.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent1/Component.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent2/Component-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/components/subcomponent2/Component.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent2/Component.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent3/Component-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/components/subcomponent3/Component.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/components/subcomponent3/Component.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/designtime/library-dbg.designtime.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/designtime/library.designtime.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/designtime/library.designtime.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/file-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/file.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/file.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/library-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/library.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/library.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/not-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/not.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/not.js.map": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/some-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/h/some.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/h/some.js.map": { + "ui5:HasDebugVariant": true + } + }, "Build manifest contains expected tags"); + + t.is(manifest.buildManifest.namespace, "library/h", + "Build manifest contains expected namespace"); + + t.is(manifest.buildManifest.timestamp, "2022-07-27T09:00:00.000Z", + "Build manifest contains expected timestamp"); + + t.is(manifest.buildManifest.version, "1.0.0", + "Build manifest contains expected version"); +}); + +test.serial("Build library.i with manifest info taken from .library and library.js", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.i", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.i", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryITree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryPreload", "minify"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.j with JSDoc build only", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.j", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.j", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + includedTasks: ["generateJsdoc"], + excludedTasks: ["*"] + }); + + // // Beautify api.json, so that it can be better read and compared + const apiJsonPath = path.join(destPath, "test-resources", "library", "j", "designtime", "api.json"); + fs.writeFileSync(apiJsonPath, JSON.stringify(JSON.parse(fs.readFileSync(apiJsonPath, "utf-8")), null, "\t")); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.i, bundling library.h", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.i", "bundle-library.h"); + const expectedPath = path.join("test", "expected", "build", "library.i", "bundle-library.h"); + + const graph = await graphFromObject({ + dependencyTree: libraryIBundlingHTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.i, bundling library.h with build manifest", async (t) => { + const libraryHDestPath = path.join(__dirname, "..", "..", "tmp", "intermediate", "library.h-for-library.i"); + const destPath = path.join("test", "tmp", "build", "library.i", "bundle-library.h-build-manifest"); + const expectedPath = path.join("test", "expected", "build", "library.i", "bundle-library.h-build-manifest"); + const resultBuildManifestPath = path.join(__dirname, + "..", "..", "tmp", "build", "library.i", "bundle-library.h-build-manifest", ".ui5", "build-manifest.json"); + + + setLogLevel("verbose"); + const graph1 = await graphFromObject({ + dependencyTree: libraryHTree + }); + graph1.setTaskRepository(taskRepository); + + await graph1.build({ + destPath: libraryHDestPath, + createBuildManifest: true + }); + + const projectTree = cloneProjectTree(libraryIBundlingHTree); + projectTree.dependencies[1].path = libraryHDestPath; + delete projectTree.dependencies[1].configuration; + + // Stub date because of timestamp in build-manifest.json + const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); + const graph2 = await graphFromObject({ + dependencyTree: projectTree + }); + graph2.setTaskRepository(taskRepository); + await graph2.build({ + destPath, + createBuildManifest: true + }); + setLogLevel("info"); + toISOStringStub.restore(); + + let expectedFiles = await findFiles(expectedPath); + // Filter out build-manifest.json for manual comparison + expectedFiles = expectedFiles.filter((filePath) => { + return !filePath.endsWith("build-manifest.json"); + }); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + + const manifest = require(resultBuildManifestPath); + + t.deepEqual(manifest.project, { + "metadata": { + "name": "library.i" + }, + "resources": { + "configuration": { + "paths": { + "src": "resources", + "test": "test-resources" + } + } + }, + "specVersion": "2.6", + "type": "library" + }, "Build manifest contains expected project configuration"); + + t.deepEqual(manifest.buildManifest.tags, { + "/resources/library/i/library-dbg.js": { + "ui5:IsDebugVariant": true + }, + "/resources/library/i/library.js": { + "ui5:HasDebugVariant": true + }, + "/resources/library/i/library.js.map": { + "ui5:HasDebugVariant": true + } + }, "Build manifest contains expected tags"); + + t.is(manifest.buildManifest.namespace, "library/i", + "Build manifest contains expected namespace"); + + t.is(manifest.buildManifest.timestamp, "2022-07-27T09:00:00.000Z", + "Build manifest contains expected timestamp"); + + t.is(manifest.buildManifest.version, "1.0.0", + "Build manifest contains expected version"); +}); + +test.serial("Build library.l", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.l", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.l", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryLTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryManifest", "generateLibraryPreload"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build theme.j even without an library", async (t) => { + const destPath = "./test/tmp/build/theme.j/dest"; + const expectedPath = "./test/expected/build/theme.j/dest"; + + const graph = await graphFromObject({ + dependencyTree: themeJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build theme.j even without an library with resources.json", async (t) => { + const destPath = "./test/tmp/build/theme.j/dest-resources-json"; + const expectedPath = "./test/expected/build/theme.j/dest-resources-json"; + + const graph = await graphFromObject({ + dependencyTree: themeJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + includedTasks: [ + "generateResourcesJson" + ], + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build theme.j with build manifest", async (t) => { + const destPath = path.join("test", "tmp", "build", "theme.j", "build-manifest"); + const expectedPath = path.join("test", "expected", "build", "theme.j", "build-manifest"); + const resultBuildManifestPath = path.join(__dirname, + "..", "..", "tmp", "build", "theme.j", "build-manifest", ".ui5", "build-manifest.json"); + + // Stub date because of timestamp in build-manifest.json + const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); + const graph = await graphFromObject({ + dependencyTree: themeJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + createBuildManifest: true + }); + toISOStringStub.restore(); + + let expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Filter out build-manifest.json for manual comparison + expectedFiles = expectedFiles.filter((filePath) => { + return !filePath.endsWith("build-manifest.json"); + }); + + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + + const manifest = require(resultBuildManifestPath); + + t.deepEqual(manifest.project, { + "metadata": { + "name": "theme.j" + }, + "resources": { + "configuration": { + "paths": { + "src": "resources", + "test": "test-resources" + } + } + }, + "specVersion": "2.6", + "type": "theme-library" + }, "Build manifest contains expected project configuration"); + + t.deepEqual(manifest.buildManifest.tags, {}, "Build manifest contains expected tags"); + + t.is(manifest.buildManifest.namespace, null, + "Build manifest contains expected namespace"); + + t.is(manifest.buildManifest.timestamp, "2022-07-27T09:00:00.000Z", + "Build manifest contains expected timestamp"); + + t.is(manifest.buildManifest.version, "1.0.0", + "Build manifest contains expected version"); +}); + +test.serial("Build library.ø", async (t) => { + const destPath = "./test/tmp/build/library.ø/dest"; + const expectedPath = path.join("test", "expected", "build", "library.ø", "dest"); + + const graph = await graphFromObject({ + dependencyTree: libraryØTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.coreBuildtime: replaceBuildtime", async (t) => { + const destPath = path.join("test", "tmp", "build", "sap.ui.core-buildtime", "dest"); + const expectedPath = path.join("test", "expected", "build", "sap.ui.core-buildtime", "dest"); + + const dateStubs = [ + sinon.stub(Date.prototype, "getFullYear").returns(2022), + sinon.stub(Date.prototype, "getMonth").returns(5), + sinon.stub(Date.prototype, "getDate").returns(20), + sinon.stub(Date.prototype, "getHours").returns(16), + sinon.stub(Date.prototype, "getMinutes").returns(30), + ]; + + const graph = await graphFromObject({ + dependencyTree: libraryCoreBuildtimeTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks: ["generateLibraryManifest", "generateLibraryPreload"] + }); + + dateStubs.forEach((stub) => stub.restore()); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library with theme configured for CSS variables", async (t) => { + const destPath = "./test/tmp/build/theme.j/dest-css-variables"; + const expectedPath = "./test/expected/build/theme.j/dest-css-variables"; + + const graph = await graphFromObject({ + dependencyTree: themeJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + cssVariables: true, + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library with theme configured for CSS variables and theme designer resources", async (t) => { + const destPath = "./test/tmp/build/theme.j/dest-css-variables-theme-designer-resources"; + const expectedPath = "./test/expected/build/theme.j/dest-css-variables-theme-designer-resources"; + + const graph = await graphFromObject({ + dependencyTree: themeJTree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + cssVariables: true, + includedTasks: ["generateThemeDesignerResources"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build theme-library with CSS variables", async (t) => { + const destPath = "./test/tmp/build/theme.library.e/dest-css-variables"; + const expectedPath = "./test/expected/build/theme.library.e/dest-css-variables"; + + const graph = await graphFromObject({ + dependencyTree: themeLibraryETree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + cssVariables: true + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build theme-library with CSS variables and theme designer resources", async (t) => { + const destPath = "./test/tmp/build/theme.library.e/dest-css-variables-theme-designer-resources"; + const expectedPath = "./test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources"; + + const graph = await graphFromObject({ + dependencyTree: themeLibraryETree + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + cssVariables: true, + includedTasks: ["generateThemeDesignerResources"] + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build library.o with terminologies and supportedLocales", async (t) => { + const destPath = path.join("test", "tmp", "build", "library.o", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.o", "dest"); + + const graph = await graphFromPackageDependencies({ + cwd: libraryOPath + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +test.serial("Build application.o with terminologies and supportedLocales", async (t) => { + const destPath = path.join("test", "tmp", "build", "application.o", "dest"); + const expectedPath = path.join("test", "expected", "build", "application.o", "dest"); + + const graph = await graphFromPackageDependencies({ + cwd: applicationOPath + }); + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath + }); + + const expectedFiles = await findFiles(expectedPath); + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + // Check for all file contents + await checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath); + t.pass(); +}); + +const libraryDTree = { + "id": "library.d", + "version": "1.0.0", + "path": libraryDPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.d", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; + +const applicationATree = { + "id": "application.a", + "version": "1.0.0", + "path": applicationAPath, + "dependencies": [ + libraryDTree, + { + "id": "library.a", + "version": "1.0.0", + "path": path.join(collectionPath, "library.a"), + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.a", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + } + }, + { + "id": "library.b", + "version": "1.0.0", + "path": path.join(collectionPath, "library.b"), + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.b", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + } + }, + { + "id": "library.c", + "version": "1.0.0", + "path": path.join(collectionPath, "library.c"), + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.c", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.a" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; + +const applicationGTree = { + "id": "application.g", + "version": "1.0.0", + "path": applicationGPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.g", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "componentPreload": { + "namespaces": [ + "application/g", + "application/g/subcomponentA", + "application/g/subcomponentB" + ] + } + } + } +}; + +const applicationGTreeWithExcludes = { + "id": "application.g", + "version": "1.0.0", + "path": applicationGPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.g", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "resources": { + "excludes": [ + "/subcomponentA/**", + "!**/manifest.json", + "/subcomponentB/**", + "/Component.js", + ] + } + } + } +}; + +const applicationGTreeComponentPreloadPaths = { + "id": "application.g", + "version": "1.0.0", + "path": applicationGPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.g", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "componentPreload": { + "paths": [ + "application/g/**/Component.js" + ] + } + } + } +}; + +const applicationHTree = { + "id": "application.h", + "version": "1.0.0", + "path": applicationHPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.h" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "bundles": [{ + "bundleDefinition": { + "name": "application/h/sectionsA/customBundle.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "application/h/sectionsA/", + "!application/h/sectionsA/section2**", + ] + }] + }, + "bundleOptions": { + "optimize": true + } + }, + { + "bundleDefinition": { + "name": "application/h/sectionsB/customBundle.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "application/h/sectionsB/" + ] + }] + }, + "bundleOptions": { + "optimize": false + } + }] + } + } +}; + +const applicationITree = { + "id": "application.i", + "version": "1.0.0", + "path": applicationIPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.i" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "bundles": [] + } + }, +}; + +const applicationJTree = { + "id": "application.j", + "version": "1.0.0", + "path": applicationJPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.j" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "bundles": [] + } + } +}; + +const applicationKTree = { + "id": "application.k", + "version": "1.0.0", + "path": applicationKPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.k", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "componentPreload": { + "namespaces": [ + "application/k", + "application/k/subcomponentA", + "application/k/subcomponentB" + ], + "excludes": [ + "application/k/**/thirdparty/", + "!application/k/subcomponentB/thirdparty/" + ] + } + } + } +}; + +const applicationKPackageSubcomponentsTree = clone(applicationKTree); +applicationKPackageSubcomponentsTree.configuration.builder = { + "componentPreload": { + "excludes": [ + "application/k/**/thirdparty/", + "!application/k/subcomponentB/thirdparty/" + ] + } +}; + +const applicationLTree = { + "id": "application.l", + "version": "1.0.0", + "path": applicationLPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "application", + "metadata": { + "name": "application.l" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "minification": { + "excludes": [ + "application/l/**/thirdparty/**", + "!application/l/subdir/thirdparty/File1.js" + ] + } + } + } +}; + +const applicationØTree = { + "id": "application.ø", + "version": "1.0.0", + "path": applicationØPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.0", + "type": "application", + "metadata": { + "name": "application.ø" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "wêbäpp" + }, + "propertiesFileSourceEncoding": "UTF-8", + } + } + } +}; + +const libraryETree = { + "id": "library.e", + "version": "1.0.0", + "path": libraryEPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.e", + "copyright": "UI development toolkit for HTML5 (OpenUI5)\n * (c) Copyright 2009-xxx SAP SE or an " + + "SAP affiliate company.\n * Licensed under the Apache License, Version 2.0 - see LICENSE.txt." + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; + +const libraryHTree = { + "id": "library.h", + "version": "1.0.0", + "path": libraryHPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.h", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "bundles": [{ + "bundleDefinition": { + "name": "library/h/customBundle.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "library/h/some.js", + "library/h/library.js", + "library/h/fi*.js", + "!library/h/components/" + ], + "resolve": false, + "renderer": false + }, { + "mode": "raw", + "filters": [ + "library/h/not.js" + ], + "resolve": true, + "sort": true, + "renderer": false + }] + }, + "bundleOptions": { + "optimize": true + } + }, { + "bundleDefinition": { + "name": "library/h/customBundle-dbg.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "library/h/some.js", + "library/h/library.js", + "library/h/fi*.js", + "!library/h/components/" + ], + "resolve": false, + "renderer": false + }, { + "mode": "raw", + "filters": [ + "library/h/not.js" + ], + "resolve": true, + "sort": true, + "renderer": false + }] + }, + "bundleOptions": { + "optimize": false + } + }], + "componentPreload": { + "namespaces": [ + "library/h/components", + "library/h/components/subcomponent1", + "library/h/components/subcomponent2", + "library/h/components/subcomponent3" + ] + } + } + } +}; + +const libraryITree = { + "id": "library.i", + "version": "1.0.0", + "path": libraryIPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + }, + cloneProjectTree(libraryDTree) + ], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.i", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; +const libraryIBundlingHTree = { + "id": "library.i", + "version": "1.0.0", + "path": libraryIPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + }, + cloneProjectTree(libraryHTree) + ], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.i", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "bundles": [{ + "bundleDefinition": { + "name": "library/i/customLibraryHBundle.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "library/h/some.js", + "library/h/library.js", + "library/h/fi*.js", + "!library/h/components/" + ], + "resolve": false, + "renderer": false + }, { + "mode": "raw", + "filters": [ + "library/h/not.js" + ], + "resolve": true, + "sort": true, + "renderer": false + }] + }, + "bundleOptions": { + "optimize": true + } + }, { + "bundleDefinition": { + "name": "library/i/customLibraryHBundle-dbg.js", + "defaultFileTypes": [".js"], + "sections": [{ + "mode": "preload", + "filters": [ + "library/h/some.js", + "library/h/library.js", + "library/h/fi*.js", + "!library/h/components/" + ], + "resolve": false, + "renderer": false + }, { + "mode": "raw", + "filters": [ + "library/h/not.js" + ], + "resolve": true, + "sort": true, + "renderer": false + }] + }, + "bundleOptions": { + "optimize": false + } + }] + } + } +}; + +const libraryJTree = { + "id": "library.j", + "version": "1.0.0", + "path": libraryJPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.j", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + }, +}; + +const libraryLTree = { + "id": "library.l", + "version": "1.0.0", + "path": libraryLPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.l", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + }, + "builder": { + "minification": { + "excludes": [ + "**/thirdparty/**", + "!**/subdir/thirdparty/File1.js" + ] + } + } + } +}; + +const libraryØTree = { + "id": "library.ø", + "version": "1.0.0", + "path": libraryØPath, + "dependencies": [ + { + "id": "sap.ui.core-evo", + "version": "1.0.0", + "path": libraryCore, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + } + } + } + } + ], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.ø", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "máin/ßrc", + "test": "máin/吉" + }, + "propertiesFileSourceEncoding": "UTF-8" + } + } + } +}; + +const libraryCoreBuildtimeTree = { + "id": "library.coreBuildtime", + "version": "1.0.0", + "path": libraryCoreBuildtime, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "library", + "metadata": { + "name": "library.coreBuildtime", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; + +const themeJTree = { + // Using @openui5/ prefix to enable execution of generateThemeDesignerResources task + "id": "@openui5/theme.j", + "version": "1.0.0", + "path": themeJPath, + "dependencies": [], + "configuration": { + "specVersion": "2.6", + "type": "theme-library", + "metadata": { + "name": "theme.j", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + } +}; + +const themeLibraryETree = { + // Using @openui5/ prefix to enable execution of generateThemeDesignerResources task + "id": "@openui5/theme.library.e.id", + "version": "1.0.0", + "path": themeLibraryEPath, + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "theme-library", + "metadata": { + "name": "theme.library.e", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + } +}; diff --git a/packages/builder/test/lib/builder/sourceMaps.js b/packages/builder/test/lib/builder/sourceMaps.js new file mode 100644 index 00000000000..0ba21c7121d --- /dev/null +++ b/packages/builder/test/lib/builder/sourceMaps.js @@ -0,0 +1,147 @@ +import test from "ava"; +import {readFile} from "node:fs/promises"; +import {fileURLToPath} from "node:url"; +import { + AnyMap, + originalPositionFor, +} from "@jridgewell/trace-mapping"; +import lineColumn from "line-column"; +import {graphFromPackageDependencies} from "@ui5/project/graph"; +import * as taskRepository from "../../../lib/tasks/taskRepository.js"; + +const applicationURL = new URL("../../fixtures/sourcemaps/test.application/", import.meta.url); +const applicationDestRootURL = new URL("../../tmp/build/sourcemaps/test.application/", import.meta.url); + +test.beforeEach((t) => { + const readDestFile = async (filePath) => { + return readFile(new URL(filePath, t.context.destURL), {encoding: "utf8"}); + }; + + t.context.assertSourceMapping = async function(t, { + generatedFilePath, + sourceFilePath, + code, + generatedCode = code, + tracedName = undefined + }) { + const generatedFile = await readDestFile(generatedFilePath); + const sourceFile = await readDestFile(sourceFilePath); + const sourceMap = JSON.parse(await readDestFile(generatedFilePath + ".map")); + const tracer = new AnyMap(sourceMap); + + const generatedCodeIndex = generatedFile.indexOf(generatedCode); + t.not(generatedCodeIndex, -1, + `Code '${generatedCode}' must be present in generated code file '${generatedFilePath}'`); + + const codeLineColumn = lineColumn(generatedFile).fromIndex(generatedCodeIndex); + + const tracedCode = originalPositionFor(tracer, { + line: codeLineColumn.line, + column: codeLineColumn.col - 1 + }); + + t.is(tracedCode.source, sourceFilePath, + `Original position of code should be found in source file '${sourceFilePath}'`); + + if (tracedName) { + t.is(tracedCode.name, tracedName); + } + // TODO: in case of bundles (sap.ui.predefine), the code below has to be adjusted + const sourceCodeIndex = lineColumn(sourceFile).toIndex(tracedCode.line, tracedCode.column + 1); + t.is( + sourceFile.substring(sourceCodeIndex, sourceCodeIndex + code.length), code, + "Code should be at right place in source file" + ); + }; +}); + +test.serial("Verify source maps (test.application)", async (t) => { + const destURL = t.context.destURL = new URL("./dest-standard-build/", applicationDestRootURL); + + const graph = await graphFromPackageDependencies({ + cwd: fileURLToPath(applicationURL) + }); + graph.setTaskRepository(taskRepository); + + await graph.build({ + destPath: fileURLToPath(destURL) + }); + + // Default mapping created via minify task + await t.context.assertSourceMapping(t, { + generatedFilePath: "JavaScriptSourceWithCopyrightPlaceholder.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "sap.ui.define(", + tracedName: "sap" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "JavaScriptSourceWithCopyrightPlaceholder.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "functionWithinJavaScriptSourceWithCopyrightPlaceholder" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "JavaScriptSourceWithCopyrightPlaceholder.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "functionCallWithinJavaScriptSourceWithCopyrightPlaceholder()", + tracedName: "functionCallWithinJavaScriptSourceWithCopyrightPlaceholder" + }); + + // Mapping from debug variant to TypeScript source + await t.context.assertSourceMapping(t, { + generatedFilePath: "TypeScriptSource-dbg.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionWithinTypeScriptSource" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "TypeScriptSource-dbg.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionCallWithinTypeScriptSource()", + tracedName: "functionCallWithinTypeScriptSource" + }); + + // Mapping from minified Javascript to TypeScript source + await t.context.assertSourceMapping(t, { + generatedFilePath: "TypeScriptSource.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionWithinTypeScriptSource" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "TypeScriptSource.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionCallWithinTypeScriptSource()", + tracedName: "functionCallWithinTypeScriptSource" + }); + + // Mapping from Component-preload.js to JavaScript Source + await t.context.assertSourceMapping(t, { + generatedFilePath: "Component-preload.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "sap.ui.define(", + generatedCode: "sap.ui.predefine(", + tracedName: "sap" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "Component-preload.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "functionWithinJavaScriptSourceWithCopyrightPlaceholder" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "Component-preload.js", + sourceFilePath: "JavaScriptSourceWithCopyrightPlaceholder-dbg.js", + code: "functionCallWithinJavaScriptSourceWithCopyrightPlaceholder()", + tracedName: "functionCallWithinJavaScriptSourceWithCopyrightPlaceholder" + }); + + // Mapping from Component-preload.js to TypeScript Source + await t.context.assertSourceMapping(t, { + generatedFilePath: "Component-preload.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionWithinTypeScriptSource" + }); + await t.context.assertSourceMapping(t, { + generatedFilePath: "Component-preload.js", + sourceFilePath: "TypeScriptSource.ts", + code: "functionCallWithinTypeScriptSource()", + tracedName: "functionCallWithinTypeScriptSource" + }); +}); diff --git a/packages/builder/test/lib/lbt/analyzer/ComponentAnalyzer.js b/packages/builder/test/lib/lbt/analyzer/ComponentAnalyzer.js new file mode 100644 index 00000000000..a6095c9edef --- /dev/null +++ b/packages/builder/test/lib/lbt/analyzer/ComponentAnalyzer.js @@ -0,0 +1,1168 @@ +import test from "ava"; +import path from "node:path"; +import ComponentAnalyzer from "../../../../lib/lbt/analyzer/ComponentAnalyzer.js"; +import sinon from "sinon"; + +function createMockPool(relPath, manifest) { + const expectedPath = path.join(relPath, "manifest.json"); + return { + async findResource(name) { + if (name !== expectedPath) { + throw new Error(`unexpected resource name: ${name}, expected ${expectedPath}`); + } + return { + async buffer() { + return JSON.stringify(manifest); + } + }; + } + }; +} + +test("routing with empty config, routes, targets", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: {}, + routes: [], + targets: {} + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Router.js" + ], "dependencies should be correct"); +}); + +test("routing with empty config, targets", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: {}, + targets: {} + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Targets.js", + "sap/ui/core/routing/Views.js" + ], "dependencies should be correct"); +}); + +test("routing with targets but no routes", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + targets: { + test: { + viewName: "App" + } + } + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Targets.js", + "sap/ui/core/routing/Views.js", + "test/view/App.view.xml" + ], "dependencies should be correct"); +}); + +test("routing with routes as array", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: [ + { + name: "test", + target: "test" + } + ], + targets: { + test: {viewName: "App"} + } + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Router.js", + "test/view/App.view.xml" + ], "dependencies should be correct"); +}); + + +test("routing with routes as object", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: { + test: { + target: "test" + } + }, + targets: { + test: {viewName: "App"} + } + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Router.js", + "test/view/App.view.xml" + ], "dependencies should be correct"); +}); + +test("routing with route with multiple targets", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: { + test: { + target: ["test1", "test2"] + } + }, + targets: { + test1: {viewName: "Master"}, + test2: {viewName: "Detail"} + } + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Router.js", + "test/view/Master.view.xml", + "test/view/Detail.view.xml" + ], "dependencies should be correct"); +}); + +test("routing with targets with local config", async (t) => { + const mockManifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: { + test1: { + target: "test1" + }, + test2: { + target: "test2" + } + }, + targets: { + test1: { + viewName: "Master", + viewType: "JS" + }, + test2: { + viewName: "Detail", + viewPath: "test.subview" + } + } + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + + t.deepEqual(mockInfo.deps, [ + "sap/ui/core/routing/Router.js", + "test/view/Master.view.js", + "test/subview/Detail.view.xml" + ], "dependencies should be correct"); +}); + +test("rootView with object", async (t) => { + const mockManifest = { + "sap.ui5": { + rootView: { + viewName: "test.view.App", + type: "JS", + async: true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, ["test/view/App.view.js"], "dependencies should be correct"); +}); + +test("rootView with object (without type, defaults to XML)", async (t) => { + const mockManifest = { + "sap.ui5": { + rootView: { + viewName: "test.view.App", + async: true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [ + "test/view/App.view.xml", + ], "dependencies should be correct"); +}); + +test("rootView with string", async (t) => { + const mockManifest = { + "sap.ui5": { + rootView: "test.view.App" + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, ["test/view/App.view.xml"], "dependencies should be correct"); +}); + +test("rootView with Typed View ('module:' prefix)", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "viewName": "module:myapp/views/MyView", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [ + "myapp/views/MyView.js", + ], "dependencies should be correct"); +}); + +test("rootView with Template View", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "viewName": "myapp.views.MyView", + "type": "Template", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [ + "myapp/views/MyView.view.tmpl", + ], "dependencies should be correct"); +}); + +test("rootView with JSON View", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "viewName": "myapp.views.MyView", + "type": "JSON", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [ + "myapp/views/MyView.view.json", + ], "dependencies should be correct"); +}); + +test("rootView with HTML View", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "viewName": "myapp.views.MyView", + "type": "HTML", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [ + "myapp/views/MyView.view.html", + ], "dependencies should be correct"); +}); + +test("rootView with unknown view type", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "viewName": "myapp.views.MyView", + "type": "InvalidType", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [], "dependencies should be correct"); +}); + +test("rootView without viewName", async (t) => { + const mockManifest = { + "sap.ui5": { + "rootView": { + "type": "XML", + "async": true + } + } + }; + + const mockPool = createMockPool("test/", mockManifest); + + const mockInfo = { + deps: [], + addDependency(name) { + this.deps.push(name); + } + }; + + const subject = new ComponentAnalyzer(mockPool); + await subject.analyze({name: path.join("test", "Component.js")}, mockInfo); + t.deepEqual(mockInfo.deps, [], "dependencies should be correct"); +}); + + +test("analyze: with Component.js", async (t) => { + const emptyPool = {}; + const analyzer = new ComponentAnalyzer(emptyPool); + const name = "sap/ui/core/Component.js"; + const moduleInfo = {}; + const result = await analyzer.analyze({name}, moduleInfo); + t.deepEqual(result, {}, "moduleInfo was not modified"); +}); + +test("analyze: with manifest", async (t) => { + const manifest = { + "sap.ui5": { + } + }; + const mockPool = { + async findResource() { + return { + buffer: async () => JSON.stringify(manifest) + }; + } + }; + + const moduleInfo = {}; + + const analyzer = new ComponentAnalyzer(mockPool); + + const stubAnalyzeManifest = sinon.stub(analyzer, "_analyzeManifest").resolves(); + + const name = "MyComponent.js"; + await analyzer.analyze({name}, moduleInfo); + + t.true(stubAnalyzeManifest.calledOnce, "_analyzeManifest was called once"); + t.deepEqual(stubAnalyzeManifest.getCall(0).args[0], manifest, + "_analyzeManifest should be called with the manifest"); +}); + +test("analyze: without manifest", async (t) => { + const mockPool = { + async findResource() { + return { + buffer: async () => { + throw new Error("Some error"); + } + }; + } + }; + + const moduleInfo = {}; + + const analyzer = new ComponentAnalyzer(mockPool); + + const stubAnalyzeManifest = sinon.stub(analyzer, "_analyzeManifest").resolves(); + + const name = "MyComponent.js"; + const result = await analyzer.analyze({name}, moduleInfo); + + t.false(stubAnalyzeManifest.called, "_analyzeManifest was not called"); + t.deepEqual(result, {}, "empty module info object expected since resource was not found (rejects)"); +}); + +test("_analyzeManifest: empty Manifest", async (t) => { + const manifest = {}; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.false(stubAddDependency.called, "addDependency was called once"); +}); + +test("_analyzeManifest: Manifest with routing and routes array", async (t) => { + const manifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: [ + { + name: "test", + target: "test" + } + ], + targets: { + test: {viewName: "App"} + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.is(stubAddDependency.callCount, 2, "addDependency was called twice"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/core/routing/Router.js", + "addDependency should be called with the router dependency name"); + t.is(stubAddDependency.getCall(1).args[0], "test/view/App.view.xml", + "addDependency should be called with the app dependency name"); +}); + +test("_analyzeManifest: Manifest with routing and routes object", async (t) => { + const manifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: { + test: { + target: "test" + } + }, + targets: { + test: {viewName: "App"} + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.is(stubAddDependency.callCount, 2, "addDependency was called twice"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/core/routing/Router.js", + "addDependency should be called with the router dependency name"); + t.is(stubAddDependency.getCall(1).args[0], "test/view/App.view.xml", + "addDependency should be called with the app dependency name"); +}); + +test("_analyzeManifest: Manifest with legacy routes object", async (t) => { + const manifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: { + test: { + pattern: "", + view: "App", + targetAggregation: "pages", + targetControl: "app", + } + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + // Note: Dependencies to views within legacy routes are not collected + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/core/routing/Router.js", + "addDependency should be called with the router dependency name"); +}); + +test("_analyzeManifest: Manifest with empty routes array", async (t) => { + const manifest = { + "sap.ui5": { + routing: { + config: { + viewPath: "test.view", + viewType: "XML" + }, + routes: [] + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/core/routing/Router.js", + "addDependency should be called with the router dependency name"); +}); + +test("_analyzeManifest: Manifest with rootview object", async (t) => { + const manifest = { + "sap.ui5": { + rootView: { + viewName: "test.view.App", + type: "JS", + async: true + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "test/view/App.view.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with rootview string", async (t) => { + const manifest = { + "sap.ui5": { + rootView: "test.view.App" + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "test/view/App.view.xml", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with dependency libs", async (t) => { + const manifest = { + "sap.ui5": { + "dependencies": { + "libs": { + "sap.ui.core": {} + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/core/library.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with dependency components", async (t) => { + const manifest = { + "sap.ui5": { + "dependencies": { + "components": { + "sap.ui.test.manifestload": {} + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/test/manifestload/Component.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with models", async (t) => { + const manifest = { + "sap.ui5": { + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/resource/ResourceModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with V2 OData model via dataSources", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/odata/v2/service", + "type": "OData", + "settings": { + "odataVersion": "2.0" + } + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/odata/v2/ODataModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with V2 OData model via dataSources (default type)", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/odata/v2/service" + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/odata/v2/ODataModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with V2 OData model via dataSources with settings (default type)", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/odata/v2/service", + "settings": {} + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/odata/v2/ODataModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with V4 OData model via dataSources", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/odata/v4/service", + "type": "OData", + "settings": { + "odataVersion": "4.0", + } + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/odata/v4/ODataModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with unknown OData version via dataSources", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/odata/v4/service", + "type": "OData", + "settings": { + "odataVersion": "5.0", + } + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); + + +test("_analyzeManifest: Manifest with JSON model via dataSources", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/json/service", + "type": "JSON" + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/json/JSONModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with XML model via dataSources", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/xml/service", + "type": "XML" + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "sap/ui/model/xml/XMLModel.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: Manifest with model via dataSources (custom type)", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/some/service", + "type": "MyType" + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); + +test("_analyzeManifest: Manifest with model (non existing/different dataSource)", async (t) => { + const manifest = { + "sap.app": { + "dataSources": { + "mainService": { + "uri": "/uri/to/some/service" + } + } + }, + "sap.ui5": { + "models": { + "": { + "dataSource": "someOtherService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); + +test("_analyzeManifest: Manifest with model (non existing dataSource)", async (t) => { + const manifest = { + "sap.ui5": { + "models": { + "": { + "dataSource": "mainService" + } + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); + +test("_analyzeManifest: Manifest with model (no type / no dataSource)", async (t) => { + const manifest = { + "sap.ui5": { + "models": { + "": {} + } + } + }; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new ComponentAnalyzer(); + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); diff --git a/packages/builder/test/lib/lbt/analyzer/JSModuleAnalyzer.js b/packages/builder/test/lib/lbt/analyzer/JSModuleAnalyzer.js new file mode 100644 index 00000000000..9c3db3c6df6 --- /dev/null +++ b/packages/builder/test/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -0,0 +1,1118 @@ +import test from "ava"; + +import fs from "node:fs"; +import path from "node:path"; +import {ecmaVersion, parseJS, VisitorKeys} from "../../../../lib/lbt/utils/parseUtils.js"; +import ModuleInfo from "../../../../lib/lbt/resources/ModuleInfo.js"; +import JSModuleAnalyzer, {EnrichedVisitorKeys} from "../../../../lib/lbt/analyzer/JSModuleAnalyzer.js"; + +const EXPECTED_MODULE_NAME = "sap/ui/testmodule.js"; + +const EXPECTED_DECLARE_DEPENDENCIES = [ + "jquery.sap.global.js", + "top/require/void.js", "top/require/var.js", "top/require/assign.js", "top/requireSync/var.js", + "top/requireSync/assign.js", "block/require/void.js", "block/require/var.js", "block/require/assign.js", + "block/requireSync/var.js", "block/requireSync/assign.js", "nested/scope/require/void.js", + "nested/scope/require/var.js", "nested/scope/require/assign.js", "nested/scope/requireSync/var.js", + "nested/scope/requireSync/assign.js", "nested/scope2/require/void.js", "nested/scope2/require/var.js", + "nested/scope2/require/assign.js", "nested/scope2/requireSync/var.js", "nested/scope2/requireSync/assign.js" +]; + +const EXPECTED_DEFINE_DEPENDENCIES_NO_LEGACY = [ + "ui5loader-autoconfig.js", + "define/arg1.js", "define/arg2.js" +]; + +const EXPECTED_DEFINE_DEPENDENCIES_WITH_LEGACY = [ + "jquery.sap.global.js", + "define/arg1.js", "define/arg2.js", + "require/arg1.js" +]; + +const NO_DEPENDENCIES = []; + +function analyze(file, name) { + if ( arguments.length === 1 ) { + name = file; + } + return new Promise( (resolve, reject) => { + file = path.join(import.meta.dirname, "..", "..", "..", "fixtures", "lbt", file); + fs.readFile(file, (err, buffer) => { + if ( err ) { + reject(err); + } + try { + const info = analyzeString(buffer.toString(), name); + resolve(info); + } catch (execErr) { + reject(execErr); + } + }); + }); +} + +function analyzeString(content, name) { + const ast = parseJS(content, {comment: true}); + const info = new ModuleInfo(name); + new JSModuleAnalyzer().analyze(ast, name, info); + return info; +} + +function assertModuleNamesEqual(t, actual, expected, msg) { + actual.sort(); + expected.sort(); + t.deepEqual(actual, expected, msg); +} + +function getConditionalDependencies(info) { + return info.dependencies.filter((dep) => info.isConditionalDependency(dep)); +} + +async function analyzeModule( + t, + file, + name, + expectedDependencies, + expectedConditionalDependencies, + expectedSubmodules, + ignoreImplicitDependencies, + rawModule +) { + // + return analyze(file, name).then( (info) => { + t.is(info.name, name, "module name should match"); + let deps = info.dependencies; + if ( ignoreImplicitDependencies ) { + deps = deps.filter((dep) => !info.isImplicitDependency(dep)); + } + if ( expectedDependencies != null ) { + assertModuleNamesEqual(t, + deps, + expectedDependencies, + "module dependencies should match"); + } + if ( expectedConditionalDependencies != null ) { + assertModuleNamesEqual(t, + getConditionalDependencies(info), + expectedConditionalDependencies, + "conditional module dependencies should match"); + } + if ( expectedSubmodules != null ) { + assertModuleNamesEqual(t, + info.subModules, + expectedSubmodules, + "submodules should match"); + } + t.false(info.dynamicDependencies, + "no use of dynamic dependencies should have been detected"); + if (rawModule) { + t.true(info.rawModule, + "raw module"); + } else { + t.false(info.rawModule, + "ui5 module"); + } + }); // .then(() => t.end(), (e) => t.fail(`failed to analyze module with error: ${e.message}`)); +} + +test("Check for consistency between VisitorKeys and EnrichedVisitorKeys", (t) => { + // This test is supposed to run and might fail when a new ecmaVersion is configured to be used + // and not all new VisitorKeys are handled by the EnrichedVisitorKeys. + + // After updating the ecmaVersion in parseUtils, this test runs and should be fixed by adding + // the relevant keys within JSModuleAnalyzer or ignoring them (e.g. like JSX*). + + // Only then, the if-clause below should be changed to the new ecmaVersion to prevent the test + // from failing when new VisitorKeys are available via espree. + + if (ecmaVersion > 2023) { + Object.keys(VisitorKeys).forEach( (type) => { + // Ignore deprecated keys: + // - ExperimentalSpreadProperty => SpreadElement + // - ExperimentalRestProperty => RestElement + // They are about to be removed, see: https://github.com/eslint/eslint-visitor-keys/pull/36 + if (type === "ExperimentalSpreadProperty" || type === "ExperimentalRestProperty") { + return; + } + + // Ignore JSX visitor-keys because they aren't used. + if (type.startsWith("JSX")) { + return; + } + + t.not(EnrichedVisitorKeys[type], undefined, `unknown estree node type '${type}', new syntax?`); + }); + } else { + t.pass("ecmaVersion is not updated. Skipping test"); + } +}); + +test("DeclareToplevel", analyzeModule, + "modules/declare_toplevel.js", EXPECTED_MODULE_NAME, EXPECTED_DECLARE_DEPENDENCIES); + +test("DeclareFunctionExprScope", analyzeModule, + "modules/declare_function_expr_scope.js", EXPECTED_MODULE_NAME, EXPECTED_DECLARE_DEPENDENCIES); + +test("DeclareFunctionInvocationScope", analyzeModule, + "modules/declare_function_invocation_scope.js", EXPECTED_MODULE_NAME, EXPECTED_DECLARE_DEPENDENCIES); + +test("DefineToplevelNamed", analyzeModule, + "modules/define_toplevel_named.js", EXPECTED_MODULE_NAME, EXPECTED_DEFINE_DEPENDENCIES_NO_LEGACY); + +test("DefineToplevelUnnamed", analyzeModule, + "modules/define_toplevel_unnamed.js", "modules/define_toplevel_unnamed.js", EXPECTED_DEFINE_DEPENDENCIES_NO_LEGACY); + +test("DefineWithLegacyCalls", analyzeModule, + "modules/define_with_legacy_calls.js", "modules/define_with_legacy_calls.js", + EXPECTED_DEFINE_DEPENDENCIES_WITH_LEGACY); + +test("OldStyleModuleWithoutDeclare", async function(t) { + await analyze("modules/no_declare_but_requires.js", null).then((info) => { + t.is(info.name, null, "module name should be null"); + t.true(info.rawModule, "raw module"); + assertModuleNamesEqual(t, + info.dependencies, + ["dependency1.js", "dependency2.js", "jquery.sap.global.js"], + "dependencies should be correct"); + }); +}); + +test("NotAnUI5Module", analyzeModule, + "modules/not_a_module.js", "modules/not_a_module.js", + NO_DEPENDENCIES, NO_DEPENDENCIES, NO_DEPENDENCIES, NO_DEPENDENCIES, true); + +test("AMDSpecialDependenciesShouldBeIgnored", async (t) => { + await analyzeModule(t, + "modules/amd_special_dependencies.js", + "modules/amd_special_dependencies.js", + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], + [], + ["utils/helper1.js", "utils/helper2.js", "utils/helper3.js"] + ); +}); + +test("AMDMultipleModulesFirstUnnamed", async (t) => { + await analyzeModule(t, + "modules/amd_multiple_modules_first_unnamed.js", + "modules/amd_multiple_modules_first_unnamed.js", + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], + [], + ["utils/helper1.js", "utils/helper2.js"] + ); +}); + +test("AMDMultipleModulesOtherThanFirstOneUnnamed", async (t) => { + await analyzeModule(t, + "modules/amd_multiple_modules_other_than_first_one_unnamed.js", + "modules/amd_multiple_modules_other_than_first_one_unnamed.js", + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], + [], + ["utils/helper1.js", "utils/helper2.js"] + ); +}); + +test("AMDMultipleNamedModulesNoneMatchingFileName", async (t) => { + await analyzeModule(t, + "modules/amd_multiple_named_modules_none_matching_filename.js", + "modules/amd_multiple_named_modules_none_matching_filename.js", + ["dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], + [], + ["utils/helper1.js", "utils/helper2.js", "utils/helper3.js"] + ); +}); + +test("AMDMultipleNamedModulesOneMatchingFileName", async (t) => { + await analyzeModule(t, + "modules/amd_multiple_named_modules_one_matching_filename.js", + "modules/amd_multiple_named_modules_one_matching_filename.js", + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], + [], + ["utils/helper1.js", "utils/helper2.js"] + ); +}); + +test("AMDMultipleUnnamedModules", async (t) => { + try { + await analyze("modules/amd_multiple_unnamed_modules.js"); + t.fail("parsing a file with multiple unnamed modules shouldn't succeed"); + } catch (error) { + t.regex(error.message, /only one of them/, + "Exception message should contain a hint on multiple unnamed modules"); + } +}); + +test("AMDSingleNamedModule", async (t) => { + await analyzeModule(t, + "modules/amd_single_named_module.js", + "alternative/name.js", + ["alternative/dep1.js", "dep2.js", "ui5loader-autoconfig.js"], + [], + [] + ); +}); + +test("AMDSingleUnnamedModule", async (t) => { + await analyzeModule(t, + "modules/amd_single_unnamed_module.js", + "modules/amd_single_unnamed_module.js", + ["modules/dep1.js", "dep2.js", "ui5loader-autoconfig.js"], + [], + [] + ); +}); + + +test("AMDMultipleModulesWithConflictBetweenNamedAndUnnamed", async (t) => { + try { + await analyze("modules/amd_multiple_modules_with_conflict_between_named_and_unnamed.js"); + t.fail("parsing a file with conflicting modules shouldn't succeed"); + } catch (error) { + t.is(error.message, "Conflicting main modules found (unnamed + named)", + "Exception message should contain a hint on conflicting modules"); + } +}); + +test("AMDMultipleModulesWithConflictBetweenUnnamedAndNamed", async (t) => { + try { + await analyze("modules/amd_multiple_modules_with_conflict_between_unnamed_and_named.js"); + t.fail("parsing a file with conflicting modules shouldn't succeed"); + } catch (error) { + t.is(error.message, "Conflicting main modules found (unnamed + named)", + "Exception message should contain a hint on conflicting modules"); + } +}); + +test("AMDMultipleModulesWithConflictBetweenTwoNamed", async (t) => { + try { + await analyze("modules/amd_multiple_modules_with_conflict_between_two_named.js"); + t.fail("parsing a file with conflicting modules shouldn't succeed"); + } catch (error) { + t.is(error.message, "Conflicting main modules found (unnamed + named)", + "Exception message should contain a hint on conflicting modules"); + } +}); + +test("OldStyleBundle", async (t) => { + await analyzeModule(t, + "modules/bundle-oldstyle.js", + "sap-ui-core.js", + [ + "jquery.sap.dom.js", + "jquery.sap.script.js", + "sap/ui/base/Object.js", + "sap/ui/base/BindingParser.js", + "sap/ui/base/EventProvider.js", + "sap/ui/base/ManagedObjectMetadata.js", + "sap/ui/model/BindingMode.js", + "sap/ui/model/CompositeBinding.js", + "sap/ui/model/Context.js", + "sap/ui/model/FormatException.js", + "sap/ui/model/ListBinding.js", + "sap/ui/model/Model.js", + "sap/ui/model/ParseException.js", + "sap/ui/model/TreeBinding.js", + "sap/ui/model/Type.js", + "sap/ui/model/ValidateException.js", + "jquery.sap.strings.js", + "sap/ui/Global.js", + "sap/ui/base/Interface.js", + "sap/ui/core/Component.js", + "sap/ui/core/Configuration.js", + "sap/ui/core/Control.js", + "sap/ui/core/Element.js", + "sap/ui/core/ElementMetadata.js", + "sap/ui/core/FocusHandler.js", + "sap/ui/core/RenderManager.js", + "sap/ui/core/ResizeHandler.js", + "sap/ui/core/ThemeCheck.js", + "sap/ui/core/UIArea.js", + "sap/ui/core/message/MessageManager.js", + "jquery.sap.mobile.js", + "jquery.sap.properties.js", + "jquery.sap.resources.js", + "jquery.sap.sjax.js" + ], + [], + [ + "sap/ui/Device.js", + "sap/ui/thirdparty/URI.js", + "sap/ui/thirdparty/es6-promise.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "jquery.sap.global.js", + "jquery.sap.act.js", + "jquery.sap.encoder.js", + "jquery.sap.events.js", + "jquery.sap.keycodes.js", + "sap/ui/base/DataType.js", + "sap/ui/base/Event.js", + "sap/ui/base/ManagedObject.js", + "sap/ui/core/Core.js", + "sap/ui/thirdparty/jquery-mobile-custom.js" + ], + /* ignoreImplicitDependencies: */ true + ); +}); + +test("OldStyleBundleV2", async (t) => { + await analyzeModule(t, + "modules/bundle-oldstyle-v2.js", + "sap-ui-core.js", + [ + "jquery.sap.dom.js", + "jquery.sap.script.js", + "sap/ui/base/Object.js", + "sap/ui/base/BindingParser.js", + "sap/ui/base/EventProvider.js", + "sap/ui/base/ManagedObjectMetadata.js", + "sap/ui/model/BindingMode.js", + "sap/ui/model/CompositeBinding.js", + "sap/ui/model/Context.js", + "sap/ui/model/FormatException.js", + "sap/ui/model/ListBinding.js", + "sap/ui/model/Model.js", + "sap/ui/model/ParseException.js", + "sap/ui/model/TreeBinding.js", + "sap/ui/model/Type.js", + "sap/ui/model/ValidateException.js", + "jquery.sap.strings.js", + "sap/ui/Global.js", + "sap/ui/base/Interface.js", + "sap/ui/core/Component.js", + "sap/ui/core/Configuration.js", + "sap/ui/core/Control.js", + "sap/ui/core/Element.js", + "sap/ui/core/ElementMetadata.js", + "sap/ui/core/FocusHandler.js", + "sap/ui/core/RenderManager.js", + "sap/ui/core/ResizeHandler.js", + "sap/ui/core/ThemeCheck.js", + "sap/ui/core/UIArea.js", + "sap/ui/core/message/MessageManager.js", + "jquery.sap.mobile.js", + "jquery.sap.properties.js", + "jquery.sap.resources.js", + "jquery.sap.sjax.js" + ], + [], + [ + "sap/ui/Device.js", + "sap/ui/thirdparty/URI.js", + "sap/ui/thirdparty/es6-promise.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "jquery.sap.global.js", + "jquery.sap.act.js", + "jquery.sap.encoder.js", + "jquery.sap.events.js", + "jquery.sap.keycodes.js", + "sap/ui/base/DataType.js", + "sap/ui/base/Event.js", + "sap/ui/base/ManagedObject.js", + "sap/ui/core/Core.js", + "sap/ui/thirdparty/jquery-mobile-custom.js" + ], + /* ignoreImplicitDependencies: */ true + ); +}); + +test("EvoBundle", async (t) => { + await analyzeModule(t, + "modules/bundle-evo.js", + "sap-ui-core.js", + [ + "sap/base/util/now.js", + "sap/base/util/Version.js", + "sap/ui/dom/getComputedStyleFix.js", + "sap/ui/dom/activeElementFix.js", + "sap/ui/dom/includeScript.js", + "sap/ui/dom/includeStylesheet.js", + "sap/ui/core/support/Hotkeys.js", + "sap/ui/security/FrameOptions.js", + "sap/ui/performance/Measurement.js", + "sap/ui/performance/trace/Interaction.js", + "sap/ui/base/syncXHRFix.js", + "sap/base/util/LoaderExtensions.js", + "sap/base/util/defineLazyProperty.js", + "sap/base/util/ObjectPath.js", + "sap/base/util/isPlainObject.js", + "sap/ui/base/Object.js", + "sap/ui/base/BindingParser.js", + "sap/ui/base/EventProvider.js", + "sap/ui/base/ManagedObjectMetadata.js", + "sap/ui/model/BindingMode.js", + "sap/ui/model/StaticBinding.js", + "sap/ui/model/CompositeBinding.js", + "sap/ui/model/Context.js", + "sap/ui/model/FormatException.js", + "sap/ui/model/ParseException.js", + "sap/ui/model/Type.js", + "sap/ui/model/ValidateException.js", + "sap/ui/base/SyncPromise.js", + "sap/ui/util/ActivityDetection.js", + "sap/base/util/deepClone.js", + "sap/base/util/deepEqual.js", + "sap/base/util/uid.js", + "sap/ui/Global.js", + "sap/ui/base/Interface.js", + "sap/ui/core/Component.js", + "sap/ui/core/Configuration.js", + "sap/ui/core/Control.js", + "sap/ui/core/Element.js", + "sap/ui/core/ElementMetadata.js", + "sap/ui/core/FocusHandler.js", + "sap/ui/core/RenderManager.js", + "sap/ui/core/ResizeHandler.js", + "sap/ui/core/ThemeCheck.js", + "sap/ui/core/UIArea.js", + "sap/ui/core/message/MessageManager.js", + "sap/ui/dom/getScrollbarSize.js", + "sap/base/i18n/ResourceBundle.js", + "sap/base/util/array/uniqueSort.js", + "sap/ui/performance/trace/initTraces.js", + "sap/base/util/isEmptyObject.js", + "sap/base/util/each.js", + "sap/ui/events/jquery/EventSimulation.js" + ], + [], + [ + "sap/ui/thirdparty/baseuri.js", + "sap/ui/thirdparty/es6-promise.js", + "sap/ui/thirdparty/es6-shim-nopromise.js", + "ui5loader.js", + "ui5loader-autoconfig.js", + "jquery.sap.global.js", + "jquery.sap.stubs.js", + "sap/base/Log.js", + "sap/base/assert.js", + "sap/ui/base/DataType.js", + "sap/ui/base/Event.js", + "sap/ui/base/ManagedObject.js", + "sap/ui/core/Core.js", + "sap/ui/thirdparty/URI.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "sap/ui/Device.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery-mobile-custom.js" + ], + /* ignoreImplicitDependencies: */ true + ); +}); + +test("Bundle", async (t) => { + const info = await analyze("modules/bundle.js"); + const expected = [ + "sap/m/CheckBox.js", + "sap/ui/core/Core.js", + "todo/Component.js", + "todo/controller/App.controller.js", + "sap/m/messagebundle.properties", + "todo/manifest.json", + "todo/model/todoitems.json", + "todo/view/App.view.xml" + ]; + t.deepEqual(info.subModules, expected, "module dependencies should match"); + t.truthy(info.dependencies.every((dep) => !info.isConditionalDependency(dep)), + "none of the dependencies must be 'conditional'"); + t.false(info.rawModule, + "ui5 module"); +}); + +test("ES6 Syntax", async (t) => { + const info = await analyze("modules/es6-syntax.js", "modules/es6-syntax.js"); + + const expected = [ + "conditional/module1.js", + "conditional/module10.js", + "conditional/module11.js", + "conditional/module2.js", + "conditional/module3.js", + "conditional/module4.js", + "conditional/module6.js", + "conditional/module7.js", + "conditional/module8.js", + "conditional/module9.js", + "static/module1.js", + "static/module11.js", + "static/module12.js", + "static/module13.js", + "static/module14.js", + "static/module2.js", + "static/module3.js", + "static/module4.js", + "static/module5.js", + "static/module6.js", + "static/module7.js", + "static/module8.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ES6 Syntax (with dynamic dependencies)", async (t) => { + const info = await analyze( + "modules/es6-syntax-dynamic-dependencies.js", + "modules/es6-syntax-dynamic-dependencies.js"); + const expected = [ + "static/module1.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.true(info.dynamicDependencies, + `use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ES6 Async Module", async (t) => { + const info = await analyze("modules/es6-async-module.js", "modules/es6-async-module.js"); + const expected = [ + "static/module1.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ES6 Template Literal", async (t) => { + const info = await analyze("modules/es6-template-literal.js", "modules/es6-template-literal.js"); + const expected = [ + "static/module1.js", + "static/module2.js", + "static/module3.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ES6 Template Literal with Expression", async (t) => { + const info = await analyze("modules/es6-template-literal-with-expression.js", + "modules/es6-template-literal-with-expression.js"); + const expected = [ + "static/module1.js", + "static/module2.js", + "static/module3.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.true(info.dynamicDependencies, + `use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ES6 Template Literal in sap.ui.predefine", async (t) => { + const info = await analyze("modules/es6-template-literal-predefine.js", + "modules/es6-template-literal-predefine.js"); + const expected = [ + "static/module1.js", + "static/module2.js", + "static/module3.js", + "ui5loader-autoconfig.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("ChainExpression", (t) => { + const content = ` + sap.ui.define(['require', 'static/module1'], (require) => { + sap?.ui?.require?.(['conditional/module2']); + sap?.ui?.requireSync?.('conditional/module3'); + jQuery?.sap?.require?.('conditional.module4'); + require?.(['conditional/module5']); + });`; + const info = analyzeString(content, "modules/ChainExpression.js"); + + const expected = [ + "conditional/module2.js", + "conditional/module3.js", + "conditional/module4.js", + "conditional/module5.js", + "jquery.sap.global.js", + "static/module1.js", + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + }); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected`); + t.false(info.rawModule, + `ui5 module`); +}); + +test("LogicalExpression", (t) => { + const content = ` + sap.ui.define(['require', 'static/module1'], (require, module1) => { + module1 && sap.ui.require(['conditional/module2']); + module1 || sap.ui.requireSync('conditional/module3'); + module1 ?? jQuery.sap.require('conditional.module4'); + !module1 && require(['conditional/module5']); + + sap.ui.require(['static/module2']) && module1; + sap.ui.requireSync('static/module3') || module1; + jQuery.sap.require('static.module4') ?? module1; + require(['static/module5']) && module1; + });`; + const info = analyzeString(content, "modules/LogicalExpression.js"); + + const expected = [ + "conditional/module2.js", + "conditional/module3.js", + "conditional/module4.js", + "conditional/module5.js", + "jquery.sap.global.js", + "static/module1.js", + "static/module2.js", + "static/module3.js", + "static/module4.js", + "static/module5.js", + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + }); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected`); + t.false(info.rawModule, + `ui5 module`); +}); + +test("ES2022: PrivateIdentifier, PropertyDefinition, StaticBlock", (t) => { + const content = ` + sap.ui.define(['require', 'static/module1'], (require) => { + + class TestES2022 { + + // Eager dependencies + + static { + const staticModule2 = sap.ui.requireSync('static/module2'); + } + + static publicStaticField = sap.ui.requireSync('static/module3'); + static #privateStaticField = sap.ui.requireSync('static/module4'); + static [sap.ui.requireSync('static/module5')] = "module5"; + + // Even though the field is on instance level, the computed key is evaluated when the class is declared + [sap.ui.requireSync('static/module6')] = "module6"; + + // Conditional dependencies + + publicField = sap.ui.requireSync('conditional/module1'); + #privateField = sap.ui.requireSync('conditional/module2'); + + #privateMethod() { + sap.ui.requireSync('conditional/module3') + } + + static #privateStaticMethod() { + sap.ui.requireSync('conditional/module4') + } + + } + + });`; + const info = analyzeString(content, "modules/ES2022.js"); + + const expected = [ + "conditional/module1.js", + "conditional/module2.js", + "conditional/module3.js", + "conditional/module4.js", + "static/module1.js", + "static/module2.js", + "static/module3.js", + "static/module4.js", + "static/module5.js", + "static/module6.js", + "ui5loader-autoconfig.js", + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + }); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected`); + t.false(info.rawModule, + `ui5 module`); +}); + +test("Dynamic import (declare/require)", async (t) => { + const info = await analyze("modules/declare_dynamic_require.js"); + t.true(info.dynamicDependencies, + "the use of dynamic dependencies should have been detected"); + t.false(info.rawModule, + "ui5 module"); +}); + +test("Conditional import (declare/require)", async (t) => { + const info = await analyze("modules/declare_require_conditional.js", + "modules/declare_require_conditional.js"); + const expected = [ + "conditional/module1.js", + "conditional/module2.js", + "jquery.sap.global.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.false(info.dynamicDependencies, + `no use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("Dynamic import (declare/require/conditional)", async (t) => { + const info = await analyze("modules/declare_dynamic_require_conditional.js", + "modules/declare_dynamic_require_conditional.js"); + const expected = [ + "conditional/module1.js", + "jquery.sap.global.js" + ]; + const actual = info.dependencies.sort(); + t.deepEqual(actual, expected, "module dependencies should match"); + expected.forEach((dep) => { + t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), + `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); + t.true(info.dynamicDependencies, + `use of dynamic dependencies should have been detected (${dep})`); + t.false(info.rawModule, + `ui5 module (${dep})`); + }); +}); + +test("Dynamic import (define/require)", async (t) => { + const info = await analyze("modules/amd_dynamic_require.js"); + t.true(info.dynamicDependencies, + "the use of dynamic dependencies should have been detected"); + t.false(info.rawModule, + "ui5 module"); +}); + +test("Dynamic import (define/requireSync)", async (t) => { + const info = await analyze("modules/amd_dynamic_require_sync.js"); + t.true(info.dynamicDependencies, + "the use of dynamic dependencies should have been detected"); + t.false(info.rawModule, + "ui5 module"); +}); + +test("Nested require", (t) => { + const content = ` +(function(deps, callback) { + function doIt(array, callback) { + callback(); + } + + var aArray = []; + doIt(aArray, function() { + doIt(["foo"], function() { + doIt(["bar"], function() { + // nested sap.ui.require + sap.ui.require(deps, callback); + }); + }); + }); +}([ + "my/dependency" +], function(myDep) { + console.log("done") +}));`; + const info = analyzeString(content, "modules/nestedRequire.js"); + t.true(info.rawModule, "raw module"); +}); + +test("Toplevel define", (t) => { + const content = ` +(function() { + function defineMyFile() { + sap.ui.define('def/MyFile', ['dep/myDep'], + function(myDep) { + return 47; + }); + } + + // conditional + if (!(window.sap && window.sap.ui && window.sap.ui.define)) { + var fnHandler = function() { + defineMyFile(); + }; + my.addEventListener("myevent", fnHandler); + } else { + defineMyFile(); + } +}()); `; + const info = analyzeString(content, "modules/functionDefine.js"); + t.true(info.rawModule, "raw module"); +}); + +test("Invalid ui5 bundle comment", (t) => { + const content = `//@ui5-bundles sap/ui/thirdparty/xxx.js +if(!('xxx'in Node.prototype)){} +//@ui5-bundle-raw-includes sap/ui/thirdparty/aaa.js +(function(g,f){g.AAA=f();}(this,(function(){}))); +sap.ui.define("my/module", ["sap/ui/core/UIComponent"],function(n){return 47+n});`; + const info = analyzeString(content, "modules/bundle-evo_invalid_comment.js"); + t.is(info.name, "my/module.js", + "module name matches"); + t.deepEqual(info.subModules, [], + "no submodules"); +}); + +test("Declare two times", (t) => { + const content = `jQuery.sap.declare("sap.ui.testmodule"); +sap.ui.testmodule.load = function(modName) { + jQuery.sap.require(modName); +}; +jQuery.sap.declare("sap.ui.testmodule");`; + const info = analyzeString(content, "modules/declare_times_two.js"); + t.is(info.name, "sap/ui/testmodule.js", + "module name matches"); + t.deepEqual(info.subModules, [], + "no submodules"); +}); + +test("Declare dynamic name", (t) => { + const content = `var sCommonName = "sap.ui" +jQuery.sap.declare(sCommonName + ".testmodule"); + +sap.ui.testmodule.load = function(modName) { + jQuery.sap.require(modName); +};`; + const info = analyzeString(content, "modules/dynamic_name.js"); + t.is(info.name, "modules/dynamic_name.js", + "module name matches"); + t.deepEqual(info.subModules, [], + "no submodules"); +}); + +test("jQuery.sap.registerPreloadedModules (with Identifier)", (t) => { + const content = ` +var data = {}; +jQuery.sap.registerPreloadedModules(data); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-Identifier.js"); + t.deepEqual(info.subModules, [], + "no submodules"); +}); + +test("jQuery.sap.registerPreloadedModules (with ObjectExpression)", (t) => { + const content = ` +jQuery.sap.registerPreloadedModules({ + "modules": { + "foo.bar": "" + } +}); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-ObjectExpression.js"); + t.deepEqual(info.subModules, ["foo/bar.js"], + "submodule from jQuery.sap.registerPreloadedModules"); +}); + +test("jQuery.sap.registerPreloadedModules (with ObjectExpression, version 1.0)", (t) => { + const content = ` +jQuery.sap.registerPreloadedModules({ + "modules": { + "foo.bar": "" + }, + "version": "1.0" +}); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-ObjectExpression.js"); + t.deepEqual(info.subModules, ["foo/bar.js"], + "submodule from jQuery.sap.registerPreloadedModules"); +}); + +test("jQuery.sap.registerPreloadedModules (with ObjectExpression, version 1.0 and SpreadExpression)", (t) => { + const content = ` +jQuery.sap.registerPreloadedModules({ + "modules": { + ...foo + }, + "version": "1.0" +}); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-ObjectExpression.js"); + t.deepEqual(info.subModules, [], + "submodule from jQuery.sap.registerPreloadedModules are empty"); +}); + +test("jQuery.sap.registerPreloadedModules (with ObjectExpression, version 2.0)", (t) => { + const content = ` +jQuery.sap.registerPreloadedModules({ + "modules": { + "foo/bar.js": "" + }, + "version": "2.0" +}); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-ObjectExpression.js"); + t.deepEqual(info.subModules, ["foo/bar.js"], + "submodule from jQuery.sap.registerPreloadedModules"); +}); + +test("jQuery.sap.registerPreloadedModules (with ObjectExpression, version 2.0 and SpreadExpression)", (t) => { + const content = ` +jQuery.sap.registerPreloadedModules({ + "modules": { + ...foo + }, + "version": "2.0" +}); +`; + const info = analyzeString(content, "modules/registerPreloadedModules-ObjectExpression.js"); + t.deepEqual(info.subModules, [], + "submodule from jQuery.sap.registerPreloadedModules are empty"); +}); + +test("Module that contains jQuery.sap.declare should be derived as subModule", (t) => { + const content = ` +sap.ui.define([], function() { + jQuery.sap.declare("foo.bar"); +}); +`; + const info = analyzeString(content, "modules/module-with-jquery-sap-declare.js"); + t.is(info.name, "modules/module-with-jquery-sap-declare.js"); + t.is(info.rawModule, false); + t.is(info.format, "ui5-declare"); // FIXME: Format should actually be ui5-define + t.is(info.requiresTopLevelScope, false); + t.deepEqual(info.subModules, ["foo/bar.js"], + "jQuery.sap.declare subModule should be detected"); + t.deepEqual(info.dependencies, ["jquery.sap.global.js"], "Implicit dependency"); +}); + +test("Bundle that contains jQuery.sap.declare (sap.ui.predefine) should not be derived as module name", (t) => { + const content = `//@ui5-bundle test1/library-preload.js +sap.ui.predefine("test1/module1", [], function() { + jQuery.sap.declare("foo.bar"); +}); +`; + const info = analyzeString(content, "modules/bundle-with-jquery-sap-declare.js"); + t.is(info.name, "test1/library-preload.js", "Module name should be taken from @ui5-bundle comment"); + t.is(info.rawModule, false); + t.is(info.format, "ui5-declare"); // FIXME: Format should actually be ui5-define + t.is(info.requiresTopLevelScope, false); + // Note: foo/bar.js is not listed as the predefine body is not analyzed + t.deepEqual(info.subModules, ["test1/module1.js"], + "subModule via sap.ui.predefine should be detected"); + t.deepEqual(info.dependencies, ["jquery.sap.global.js"], "Implicit dependency"); +}); + +test("Bundle that contains jQuery.sap.declare (sap.ui.require.preload) should not be derived as module name", (t) => { + const content = `//@ui5-bundle test1/library-preload.js +sap.ui.require.preload({ + "test1/module1.js": function() { + sap.ui.define([], function() { + jQuery.sap.declare("foo.bar"); + }); + } +}); + +`; + const info = analyzeString(content, "modules/bundle-with-jquery-sap-declare.js"); + t.is(info.name, "test1/library-preload.js", "Module name should be taken from @ui5-bundle comment"); + t.is(info.rawModule, false); + t.is(info.format, "ui5-define"); + t.is(info.requiresTopLevelScope, false); + // Note: foo/bar.js is not listed as the sap.ui.define body is not analyzed + t.deepEqual(info.subModules, ["test1/module1.js"], + "subModule via sap.ui.predefine should be detected"); + t.deepEqual(info.dependencies, ["ui5loader-autoconfig.js"], "Implicit dependency"); +}); + +test("@ui5-bundle comment: Multiple comments", (t) => { + const content = `//@ui5-bundle test/bundle1.js +//@ui5-bundle test/bundle2.js +`; + const info = analyzeString(content, "modules/ui5-bundle-comments.js"); + t.is(info.name, "test/bundle1.js", "Comment from first line should be used"); + t.deepEqual(info.subModules, []); + t.deepEqual(info.dependencies, []); +}); + +test("@ui5-bundle comment: Multiple comments (Not in first line)", (t) => { + const content = `console.log('Foo'); +//@ui5-bundle test/bundle1.js +//@ui5-bundle test/bundle2.js +`; + t.throws(() => analyzeString(content, "modules/ui5-bundle-comments.js"), { + message: "Conflicting main modules found (unnamed + named)" + }); +}); diff --git a/packages/builder/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js b/packages/builder/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js new file mode 100644 index 00000000000..9ff92b03e35 --- /dev/null +++ b/packages/builder/test/lib/lbt/analyzer/SmartTemplateAnalyzer.js @@ -0,0 +1,814 @@ +import test from "ava"; +import ModuleInfo from "../../../../lib/lbt/resources/ModuleInfo.js"; +import {parseJS} from "../../../../lib/lbt/utils/parseUtils.js"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.sinon = sinonGlobal.createSandbox(); + t.context.SmartTemplateAnalyzer = await esmock("../../../../lib/lbt/analyzer/SmartTemplateAnalyzer.js"); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test("analyze: with Component.js", async (t) => { + const {SmartTemplateAnalyzer} = t.context; + const emptyPool = {}; + const analyzer = new SmartTemplateAnalyzer(emptyPool); + const name = "sap/ui/core/Component.js"; + const moduleInfo = {}; + const result = await analyzer.analyze({name}, moduleInfo); + t.deepEqual(result, {}, "moduleInfo was not modified"); +}); + +test("analyze: without manifest", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + const mockPool = { + async findResource() { + return { + buffer: async () => { + throw new Error("Some error"); + } + }; + } + }; + + const moduleInfo = {}; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + const stubAnalyzeManifest = sinon.stub(analyzer, "_analyzeManifest").resolves(); + + const name = "MyComponent.js"; + const result = await analyzer.analyze({name}, moduleInfo); + + t.false(stubAnalyzeManifest.called, "_analyzeManifest was not called"); + t.deepEqual(result, {}, "empty module info object expected since resource was not found (rejects)"); +}); + +test("_analyzeManifest: without manifest", async (t) => { + const {SmartTemplateAnalyzer} = t.context; + + const moduleInfo = {}; + + const analyzer = new SmartTemplateAnalyzer(); + + const result = await analyzer._analyzeManifest({}, moduleInfo); + + t.deepEqual(result, [], "resolves with empty array"); +}); + + +test("_analyzeManifest: with manifest with recursive pages (as array)", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const manifest = { + "sap.ui.generic.app": { + "pages": [{ + "component": { + "name": "test.mycomp", + "settings": { + "templateName": "myTemplate" + } + }, + "pages": [{ + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + }, + }] + }] + } + }; + + const analyzer = new SmartTemplateAnalyzer(); + const stubAnalyzeTemplateComponent = sinon.stub(analyzer, "_analyzeTemplateComponent").resolves(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.is(stubAnalyzeTemplateComponent.callCount, 2, "_analyzeTemplateComponent was called twice"); + t.is(stubAnalyzeTemplateComponent.getCall(0).args[0], "test/mycomp/Component.js", + "_analyzeTemplateComponent should be called with the component"); + t.deepEqual(stubAnalyzeTemplateComponent.getCall(0).args[1], { + "component": { + "name": "test.mycomp", + "settings": { + "templateName": "myTemplate" + } + }, + "pages": [{ + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + }, + }] + }, "_analyzeTemplateComponent should be called with the page"); + + t.is(stubAnalyzeTemplateComponent.getCall(1).args[0], "test/mycomp2/Component.js", + "_analyzeTemplateComponent should be called with the component"); + t.deepEqual(stubAnalyzeTemplateComponent.getCall(1).args[1], { + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + } + }, "_analyzeTemplateComponent should be called with the page"); + + t.is(stubAddDependency.callCount, 2, "addDependency was called twice"); + t.is(stubAddDependency.getCall(0).args[0], "test/mycomp/Component.js", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeManifest: with manifest with recursive pages (as object)", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const manifest = { + "sap.ui.generic.app": { + "pages": { + "MyPage1": { + "component": { + "name": "test.mycomp", + "settings": { + "templateName": "myTemplate" + } + }, + "pages": { + "MyPage2": { + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + } + } + } + } + } + } + }; + + const analyzer = new SmartTemplateAnalyzer(); + const stubAnalyzeTemplateComponent = sinon.stub(analyzer, "_analyzeTemplateComponent").resolves(); + + await analyzer._analyzeManifest(manifest, moduleInfo); + + t.is(stubAnalyzeTemplateComponent.callCount, 2, "_analyzeTemplateComponent was called twice"); + t.is(stubAnalyzeTemplateComponent.getCall(0).args[0], "test/mycomp/Component.js", + "_analyzeTemplateComponent should be called with the component"); + t.deepEqual(stubAnalyzeTemplateComponent.getCall(0).args[1], { + "component": { + "name": "test.mycomp", + "settings": { + "templateName": "myTemplate" + } + }, + "pages": { + "MyPage2": { + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + } + } + } + }, "_analyzeTemplateComponent should be called with the page"); + + t.is(stubAnalyzeTemplateComponent.getCall(1).args[0], "test/mycomp2/Component.js", + "_analyzeTemplateComponent should be called with the component"); + t.deepEqual(stubAnalyzeTemplateComponent.getCall(1).args[1], { + "component": { + "name": "test.mycomp2", + "settings": { + "templateName": "myTemplate" + } + } + }, "_analyzeTemplateComponent should be called with the page"); + + t.is(stubAddDependency.callCount, 2, "addDependency was called twice"); + t.is(stubAddDependency.getCall(0).args[0], "test/mycomp/Component.js", + "addDependency should be called with the dependency name"); +}); + +test.serial("_analyzeTemplateComponent: Manifest with TemplateAssembler code", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const mockPool = { + async findResource() { + return { + buffer: async () => "" + }; + }, + getIgnoreMissingModules() { + return false; + } + }; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + + const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns("mytpl"); + + await analyzer._analyzeTemplateComponent("pony", + {}, moduleInfo); + + t.true(stubAnalyzeAST.calledOnce, "_analyzeManifest was called once"); + t.is(stubAnalyzeAST.getCall(0).args[0], "pony", + "_analyzeManifest should be called with the module name"); + + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "mytpl.view.xml", + "addDependency should be called with the dependency name"); +}); + +test.serial("_analyzeTemplateComponent: no default template name", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const mockPool = { + async findResource() { + return { + buffer: async () => "" + }; + }, + getIgnoreMissingModules() { + return false; + } + }; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + + const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); + + await analyzer._analyzeTemplateComponent("pony", + {}, moduleInfo); + + t.true(stubAnalyzeAST.calledOnce, "_analyzeManifest was called once"); + + t.true(stubAddDependency.notCalled, "addDependency was not called"); +}); + +test.serial("_analyzeTemplateComponent: with template name from pageConfig", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const mockPool = { + async findResource() { + return { + buffer: async () => "" + }; + }, + getIgnoreMissingModules() { + return false; + } + }; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + + const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); + + await analyzer._analyzeTemplateComponent("pony", { + component: { + settings: { + templateName: "donkey" + } + } + }, moduleInfo); + + t.true(stubAnalyzeAST.calledOnce, "_analyzeManifest was called once"); + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "donkey.view.xml", + "addDependency should be called with the dependency name"); +}); + +test.serial("_analyzeTemplateComponent: dependency not found", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const mockPool = { + async findResource() { + throw new Error(`resource not found in pool: 'pony'`); + }, + getIgnoreMissingModules() { + return false; + } + }; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + + const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); + + const error = await t.throwsAsync(analyzer._analyzeTemplateComponent("pony", { + component: { + settings: { + templateName: "donkey" + } + } + }, moduleInfo)); + + t.is(error.message, `resource not found in pool: 'pony'`); + + t.is(stubAnalyzeAST.callCount, 0, "_analyzeManifest was not called"); + + t.is(stubAddDependency.callCount, 0, "addDependency was not called"); +}); + +test.serial("_analyzeTemplateComponent: dependency not found is ignored", async (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = sinon.spy(moduleInfo, "addDependency"); + + const mockPool = { + async findResource() { + throw new Error(`resource not found in pool: 'pony'`); + }, + getIgnoreMissingModules() { + return true; // Missing dependency error can be ignored + } + }; + + const analyzer = new SmartTemplateAnalyzer(mockPool); + + const stubAnalyzeAST = sinon.stub(analyzer, "_analyzeAST").returns(""); + + await analyzer._analyzeTemplateComponent("pony", { + component: { + settings: { + templateName: "donkey" + } + } + }, moduleInfo); + + t.is(stubAnalyzeAST.callCount, 0, "_analyzeManifest was not called"); + + t.is(stubAddDependency.callCount, 0, "addDependency was not called"); +}); + +test("_analyzeAST: get template name from ast", (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + function(a, TemplateAssembler){ + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + + const analyzer = new SmartTemplateAnalyzer(); + + const stubAnalyzeTemplateClassDefinition = sinon.stub(analyzer, + "_analyzeTemplateClassDefinition").returns("donkey"); + + const result = analyzer._analyzeAST("pony", ast); + + + t.true(stubAnalyzeTemplateClassDefinition.calledOnce, "_analyzeTemplateClassDefinition was called once"); + + stubAnalyzeTemplateClassDefinition.restore(); + t.is(result, "donkey"); +}); + +test("_analyzeAST: get template name from ast (AMD define)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + + const code = `define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], function(a, TemplateAssembler) { + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page"); +}); + +test("_analyzeAST: unable to get template name from ast (no TemplateAssembler import)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + + const code = `define(["a"], function(a, TemplateAssembler) { // import missing + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, ""); +}); + +test("_analyzeAST: unable to get template name from ast (no module definition)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + + const code = `myDefine(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + function(a, TemplateAssembler) { // unsupported module definition + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, ""); +}); + +test("_analyzeAST: get template name from ast (ArrowFunction)", (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => { + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + + const analyzer = new SmartTemplateAnalyzer(); + + const stubAnalyzeTemplateClassDefinition = sinon.stub(analyzer, + "_analyzeTemplateClassDefinition").returns("donkey"); + + const result = analyzer._analyzeAST("pony", ast); + + + t.true(stubAnalyzeTemplateClassDefinition.calledOnce, "_analyzeTemplateClassDefinition was called once"); + + stubAnalyzeTemplateClassDefinition.restore(); + t.is(result, "donkey"); +}); + +test("_analyzeAST: get template name from ast (ArrowFunction with implicit return)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + }));`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page"); +}); + +test.serial("_analyzeAST: get template name from ast (async factory function)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + async function (a, TemplateAssembler) { + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + } + );});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page"); +}); + +test("_analyzeAST: unable to get template name from ast (ArrowFunction with implicit return #1)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + // No templateName provided + "manifest": "json" + } + }));`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, ""); +}); + +test("_analyzeAST: unable to get template name from ast (ArrowFunction with implicit return #2)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => TemplateAssembler.extend(getMethods, // wrong call. should be 'getTemplateComponent' + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + }));`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, ""); +}); + +test.serial("_analyzeAST: get template name from ast (async arrow factory function)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + async (a, TemplateAssembler) => { + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + } + );});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page"); +}); + +test.serial("_analyzeAST: get template name from ast (async arrow factory function implicit return)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + async (a, TemplateAssembler) => TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + } + ));`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page"); +}); + +test("_analyzeAST: get template name from ast (with SpreadElement)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => { + const myTemplate = { + templateName: { + type: "string", + defaultValue: "sap.fe.templates.Page.view.Page" + } + }; + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + ...myTemplate + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + + t.is(templateName, "", "The TemplateName is correctly empty as SpreadElements are not supported"); +}); + + +test("_analyzeAST: no template name from ast", (t) => { + const {sinon, SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + function(a, TemplateAssembler){ + return TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + });});`; + const ast = parseJS(code); + + const analyzer = new SmartTemplateAnalyzer(); + + const stubAnalyzeTemplateClassDefinition = sinon.stub(analyzer, + "_analyzeTemplateClassDefinition").returns(false); + + const result = analyzer._analyzeAST("pony", ast); + + + t.true(stubAnalyzeTemplateClassDefinition.calledOnce, "_analyzeTemplateClassDefinition was called once"); + + stubAnalyzeTemplateClassDefinition.restore(); + t.is(result, ""); +}); + +test("_analyzeAST: get template name (template literal)", (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + (a, TemplateAssembler) => TemplateAssembler.getTemplateComponent(getMethods, + "sap.fe.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": \`sap.fe.templates.Page.view.Page\` + } + }, + "manifest": "json" + } + }));`; + const ast = parseJS(code); + const analyzer = new SmartTemplateAnalyzer(); + const templateName = analyzer._analyzeAST("sap.fe.templates.Page.Component", ast); + t.is(templateName, "sap.fe.templates.Page.view.Page", "The TemplateName is correct"); +}); + +test("Analysis of Manifest and TemplateAssembler code", async (t) => { + const {SmartTemplateAnalyzer} = t.context; + const manifest = { + "sap.ui.generic.app": { + "pages": [{ + "component": { + "name": "test.mycomp", + "settings": { + "templateName": "myTemplate" + } + }, + }] + } + }; + + const code = `sap.ui.define(["a", "sap/suite/ui/generic/template/lib/TemplateAssembler"], + function(a, TemplateAssembler){ return TemplateAssembler.getTemplateComponent(getMethods, + "sap.suite.ui.generic.templates.Page.Component", { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.suite.ui.generic.templates.Page.view.Page" + } + }, + "manifest": "json" + } + }); + });`; + const mockPool = { + async findResource(name) { + return { + buffer: () => name.endsWith(".json") ? JSON.stringify(manifest): code + }; + } + }; + + const moduleInfo = new ModuleInfo(); + + const analyzer = new SmartTemplateAnalyzer(mockPool); + const name = "MyComponent.js"; + await analyzer.analyze({name}, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["test/mycomp/Component.js", "myTemplate.view.xml"], + "Resulting dependencies should come from manifest and code"); +}); + +test("_analyzeTemplateClassDefinition: get template name from metadata", async (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `var x = { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": "sap.fe.templates.Page.view.Page" + } + }, + "manifest": "json" + } + };`; + const ast = parseJS(code); + const expression = ast.body[0].declarations[0].init; + + const analyzer = new SmartTemplateAnalyzer(); + + const result = await analyzer._analyzeTemplateClassDefinition(expression); + + t.is(result, "sap.fe.templates.Page.view.Page", "defaultValue is retrieved"); +}); + +test("_analyzeTemplateClassDefinition: no string template name from metadata", async (t) => { + const {SmartTemplateAnalyzer} = t.context; + const code = `var x = { + metadata: { + properties: { + "templateName": { + "type": "string", + "defaultValue": false + } + }, + "manifest": "json" + } + };`; + const ast = parseJS(code); + const expression = ast.body[0].declarations[0].init; + + const analyzer = new SmartTemplateAnalyzer(); + + const result = await analyzer._analyzeTemplateClassDefinition(expression); + + t.falsy(result, "defaultValue is not a string"); +}); diff --git a/packages/builder/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js b/packages/builder/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js new file mode 100644 index 00000000000..8e5d773d8d6 --- /dev/null +++ b/packages/builder/test/lib/lbt/analyzer/XMLCompositeAnalyzer.js @@ -0,0 +1,283 @@ +import test from "ava"; +import {parseJS} from "../../../../lib/lbt/utils/parseUtils.js"; +import XMLCompositeAnalyzer from "../../../../lib/lbt/analyzer/XMLCompositeAnalyzer.js"; +import ModuleInfo from "../../../../lib/lbt/resources/ModuleInfo.js"; +import sinonGlobal from "sinon"; + +test.beforeEach((t) => { + t.context.sinon = sinonGlobal.createSandbox(); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test("integration: XMLComposite code with VariableDeclaration", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + function(jQuery, XMLComposite) { + "use strict"; + var ButtonList = XMLComposite.extend("composites.ButtonList", {}); + return ButtonList; + });`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test("integration: XMLComposite code", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + function(jQuery, XMLComposite) { + "use strict"; + return XMLComposite.extend("composites.ButtonList", {}); + });`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test("integration: XMLComposite code (arrow factory function)", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + (jQuery, XMLComposite) => { + return XMLComposite.extend("composites.ButtonList", {}); + });`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test("integration: XMLComposite code (arrow factory function with implicit return)", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + (jQuery, XMLComposite) => XMLComposite.extend("composites.ButtonList", {}));`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test.serial("integration: XMLComposite code (async factory function)", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + async function(jQuery, XMLComposite) { + "use strict"; + return XMLComposite.extend("composites.ButtonList", {}); + });`; + const ast = parseJS(code); + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test.serial("integration: XMLComposite code (async arrow factory function)", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + async (jQuery, XMLComposite) => { + return XMLComposite.extend("composites.ButtonList", {}); + });`; + const ast = parseJS(code); + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test.serial("integration: XMLComposite code (async arrow factory function with implicit return)", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + async (jQuery, XMLComposite) => XMLComposite.extend("composites.ButtonList", {}));`; + const ast = parseJS(code); + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name"); +}); + +test("integration: XMLComposite code with SpreadElement", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + (jQuery, XMLComposite) => { + const myXMLComposite = { + fragment: "composites.custom.ButtonList" + }; + return XMLComposite.extend("composites.ButtonList", { + ...myXMLComposite + }); + });`; + + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const name = "composites.ButtonList"; + const moduleInfo = new ModuleInfo(); + analyzer.analyze(ast, name, moduleInfo); + + t.deepEqual(moduleInfo.dependencies, ["composites/ButtonList.control.xml"], + "Dependency should be created from composite name because overridden by the 'fragment' property " + + " is not possible to lacking SpreadElement support"); +}); + +test("analyze: not an XMLComposite module", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + function(jQuery, XMLComposite) { + "use strict"; + return {}; + });`; + + const ast = parseJS(code); + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = t.context.sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new XMLCompositeAnalyzer(); + const stubCheckForXMLCClassDefinition = + t.context.sinon.stub(analyzer, "_checkForXMLCClassDefinition").returns("cow"); + const name = "composites.ButtonList"; + analyzer.analyze(ast, name, moduleInfo); + t.false(stubCheckForXMLCClassDefinition.called, "_checkForXMLCClassDefinition was not called"); + t.false(stubAddDependency.called, "addDependency was not called"); +}); + +test("analyze: XMLComposite VariableDeclaration code", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + function(jQuery, XMLComposite) { + "use strict"; + var ButtonList = XMLComposite.extend("composites.ButtonList", {}); + return ButtonList; + });`; + + const ast = parseJS(code); + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = t.context.sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new XMLCompositeAnalyzer(); + const stubCheckForXMLCClassDefinition = + t.context.sinon.stub(analyzer, "_checkForXMLCClassDefinition").returns("cow"); + const name = "composites.ButtonList"; + analyzer.analyze(ast, name, moduleInfo); + t.true(stubCheckForXMLCClassDefinition.calledOnce, "_checkForXMLCClassDefinition was called once"); + t.is(stubCheckForXMLCClassDefinition.getCall(0).args[0], "XMLComposite", + "_checkForXMLCClassDefinition should be called with the name"); + + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "cow.control.xml", + "addDependency should be called with the dependency name"); +}); + + +test("analyze: XMLComposite Expression code", (t) => { + const code = `sap.ui.define([ + 'jquery.sap.global', 'sap/ui/core/XMLComposite'], + function(jQuery, XMLComposite) { + "use strict"; + jQuery.sap.test = XMLComposite.extend("composites.ButtonList", {}); + });`; + + const ast = parseJS(code); + + const moduleInfo = { + addDependency: function() {} + }; + const stubAddDependency = t.context.sinon.spy(moduleInfo, "addDependency"); + + const analyzer = new XMLCompositeAnalyzer(); + const stubCheckForXMLCClassDefinition = + t.context.sinon.stub(analyzer, "_checkForXMLCClassDefinition").returns("cow"); + const name = "composites.ButtonList"; + analyzer.analyze(ast, name, moduleInfo); + t.true(stubCheckForXMLCClassDefinition.calledOnce, "_checkForXMLCClassDefinition was called once"); + t.is(stubCheckForXMLCClassDefinition.getCall(0).args[0], "XMLComposite", + "_checkForXMLCClassDefinition should be called with the name"); + + + t.true(stubAddDependency.calledOnce, "addDependency was called once"); + t.is(stubAddDependency.getCall(0).args[0], "cow.control.xml", + "addDependency should be called with the dependency name"); +}); + +test("_checkForXMLCClassDefinition: string argument and object expression", (t) => { + const code = `XMLComposite.extend("composites.ButtonList", {})`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const stubAnalyzeXMLCClassDefinition = t.context.sinon.stub(analyzer, "_analyzeXMLCClassDefinition").returns("cow"); + const result = analyzer._checkForXMLCClassDefinition("XMLComposite", ast.body[0].expression); + t.true(stubAnalyzeXMLCClassDefinition.calledOnce, "_checkForXMLCClassDefinition was called once"); + + t.is(result, "cow", + "addDependency should be called with the dependency name"); +}); + +test("_checkForXMLCClassDefinition: string argument (template literal)", (t) => { + const code = `XMLComposite.extend(\`composites.ButtonList\`, {})`; + const ast = parseJS(code); + const analyzer = new XMLCompositeAnalyzer(); + const fragmentName = analyzer._checkForXMLCClassDefinition("XMLComposite", ast.body[0].expression); + t.is(fragmentName, "composites.ButtonList"); +}); + +test("_analyzeXMLCClassDefinition: name retrieval", (t) => { + const code = `test({fragment: "cat"})`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const result = analyzer._analyzeXMLCClassDefinition(ast.body[0].expression.arguments[0]); + + t.is(result, "cat", + "addDependency should be called with the dependency name"); +}); + +test("_analyzeXMLCClassDefinition: name retrieval (template literal)", (t) => { + const code = `test({fragment: \`cat\`})`; + + const ast = parseJS(code); + + const analyzer = new XMLCompositeAnalyzer(); + const result = analyzer._analyzeXMLCClassDefinition(ast.body[0].expression.arguments[0]); + + t.is(result, "cat", + "addDependency should be called with the dependency name"); +}); diff --git a/packages/builder/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js b/packages/builder/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js new file mode 100644 index 00000000000..1bcee4421a2 --- /dev/null +++ b/packages/builder/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js @@ -0,0 +1,604 @@ +import test from "ava"; +import XMLTemplateAnalyzer from "../../../../lib/lbt/analyzer/XMLTemplateAnalyzer.js"; +import ModuleInfo from "../../../../lib/lbt/resources/ModuleInfo.js"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.afterEach.always((t) => { + sinon.restore(); +}); + + +const fakeMockPool = { + findResource: () => Promise.resolve() +}; + +test("integration: Analysis of an xml view", async (t) => { + const xml = ` + + + + + + + `; + + const moduleInfo = new ModuleInfo(); + + const analyzer = new XMLTemplateAnalyzer(fakeMockPool); + await analyzer.analyzeView(xml, moduleInfo); + t.deepEqual(moduleInfo.dependencies, + [ + "sap/ui/core/mvc/XMLView.js", + "myController.controller.js", + "sap/ui/layout/HorizontalLayout.js", + "sap/m/Button.js" + ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); +}); + +test("integration: Analysis of an xml view with data binding in properties", async (t) => { + const xml = ` + + `; + + const moduleInfo = new ModuleInfo(); + + const analyzer = new XMLTemplateAnalyzer(fakeMockPool); + await analyzer.analyzeView(xml, moduleInfo); + t.deepEqual(moduleInfo.dependencies, + [ + "sap/ui/core/mvc/XMLView.js", + "myController.controller.js", + "sap/ui/core/ComponentContainer.js" + ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); +}); + +test.serial("integration: Analysis of an xml view with core:require from databinding", async (t) => { + const errorLogStub = sinon.stub(); + const myLoggerInstance = { + error: errorLogStub, + verbose: sinon.stub() + }; + const XMLTemplateAnalyzerWithStubbedLogger = await esmock("../../../../lib/lbt/analyzer/XMLTemplateAnalyzer.js", { + "@ui5/logger": { + getLogger: sinon.stub().returns(myLoggerInstance) + } + }); + + const xml = ` + + + + + + + + + + + + + + + + + + + ` + }); + const myAppButtonResource = createResource({ + path: "/resources/my/app/controls/Button.js", + string: "" + }); + const myLibButtonResource = createResource({ + path: "/resources/my/lib/Button.js", + string: "" + }); + + const resourcesJson = await resourceListCreator({ + resources: [myAppManifestJsonResource, myAppXmlViewResource, myAppButtonResource], + dependencyResources: [myLibButtonResource] + }); + + t.is(resourceListCreatorLog.error.callCount, 0); + t.is(resourceListCreatorLog.verbose.callCount, 2); + t.deepEqual(resourceListCreatorLog.verbose.getCall(0).args, + ["\tFound 3 resources"]); + t.deepEqual(resourceListCreatorLog.verbose.getCall(1).args, + ["\tWriting 'my/app/resources.json'"]); + + t.is(resourcesJson.length, 1, "One resources.json should be returned"); + const myAppResourcesJson = resourcesJson[0]; + t.is(myAppResourcesJson.getPath(), "/resources/my/app/resources.json"); + const myAppResourcesJsonContent = await myAppResourcesJson.getString(); + t.is(myAppResourcesJsonContent, `{ + "_version": "1.1.0", + "resources": [ + { + "name": "controls/Button.js", + "module": "my/app/controls/Button.js", + "size": 0, + "format": "raw" + }, + { + "name": "manifest.json", + "module": "my/app/manifest.json", + "size": 27 + }, + { + "name": "resources.json", + "size": 523 + }, + { + "name": "view/Main.view.xml", + "module": "my/app/view/Main.view.xml", + "size": 592, + "required": [ + "my/app/controller/Main.controller.js", + "my/app/controls/Button.js", + "my/lib/Button.js" + ] + } + ] +}`); +}); + +test.serial("Bundle containing an XML View with control resource as dependency", async (t) => { + const {resourceListCreator, resourceListCreatorLog, ResourceCollectorLog} = t.context; + + const myAppManifestJsonResource = createResource({ + path: "/resources/my/app/manifest.json", + string: JSON.stringify({"sap.app": {"id": "my.app"}}) + }); + const myAppXmlViewResource = createResource({ + path: "/resources/my/app/view/Main.view.xml", + string: ` + + + + + + + + + + + + + + ` + }); + // eslint-disable-next-line max-len + const bundledXmlView = ``; + const myAppBundleResource = createResource({ + path: "/resources/my/app/bundle.js", + string: `//@ui5-bundle my/app/bundle.js +sap.ui.require.preload({ + "my/app/view/Main.view.xml": '${bundledXmlView}', + "my/app/controls/Button.js": '' +}); +` + }); + + const myAppButtonResource = createResource({ + path: "/resources/my/app/controls/Button.js", + string: "" + }); + const myLibButtonResource = createResource({ + path: "/resources/my/lib/Button.js", + string: "" + }); + + const resourcesJson = await resourceListCreator({ + resources: [myAppManifestJsonResource, myAppXmlViewResource, myAppButtonResource, myAppBundleResource], + dependencyResources: [myLibButtonResource] + }); + + t.is(resourceListCreatorLog.error.callCount, 0); + t.is(resourceListCreatorLog.verbose.callCount, 2); + t.deepEqual(resourceListCreatorLog.verbose.getCall(0).args, + ["\tFound 4 resources"]); + t.deepEqual(resourceListCreatorLog.verbose.getCall(1).args, + ["\tWriting 'my/app/resources.json'"]); + + t.is(ResourceCollectorLog.error.callCount, 0); + t.is(ResourceCollectorLog.warn.callCount, 0); + t.is(ResourceCollectorLog.verbose.callCount, 1); + t.deepEqual(ResourceCollectorLog.verbose.getCall(0).args, + [" Configured external resources filters (resources outside the namespace): (none)"]); + + t.is(resourcesJson.length, 1, "One resources.json should be returned"); + const myAppResourcesJson = resourcesJson[0]; + t.is(myAppResourcesJson.getPath(), "/resources/my/app/resources.json"); + const myAppResourcesJsonContent = await myAppResourcesJson.getString(); + t.is(myAppResourcesJsonContent, `{ + "_version": "1.1.0", + "resources": [ + { + "name": "bundle.js", + "module": "my/app/bundle.js", + "size": 401, + "merged": true, + "required": [ + "my/app/controller/Main.controller.js", + "my/lib/Button.js" + ], + "included": [ + "my/app/view/Main.view.xml", + "my/app/controls/Button.js" + ] + }, + { + "name": "controls/Button.js", + "module": "my/app/controls/Button.js", + "size": 0, + "format": "raw" + }, + { + "name": "manifest.json", + "module": "my/app/manifest.json", + "size": 27 + }, + { + "name": "resources.json", + "size": 801 + }, + { + "name": "view/Main.view.xml", + "module": "my/app/view/Main.view.xml", + "size": 592, + "required": [ + "my/app/controller/Main.controller.js", + "my/app/controls/Button.js", + "my/lib/Button.js" + ] + } + ] +}`); +}); + +test.serial("Bundle containing subModule which is not available within provided resources", async (t) => { + const {resourceListCreator, resourceListCreatorLog, ResourceCollectorLog} = t.context; + + const myAppManifestJsonResource = createResource({ + path: "/resources/my/app/manifest.json", + string: JSON.stringify({"sap.app": {"id": "my.app"}}) + }); + + const myAppBundleResource = createResource({ + path: "/resources/my/app/bundle.js", + string: `//@ui5-bundle my/app/bundle.js +sap.ui.require.preload({ + "my/app/view/Main.view.xml": '' +}); +` + }); + + const resourcesJson = await resourceListCreator({ + resources: [myAppManifestJsonResource, myAppBundleResource] + }); + + t.is(resourceListCreatorLog.error.callCount, 0); + t.is(resourceListCreatorLog.verbose.callCount, 2); + t.deepEqual(resourceListCreatorLog.verbose.getCall(0).args, + ["\tFound 2 resources"]); + t.deepEqual(resourceListCreatorLog.verbose.getCall(1).args, + ["\tWriting 'my/app/resources.json'"]); + + t.is(ResourceCollectorLog.error.callCount, 0); + t.is(ResourceCollectorLog.warn.callCount, 0); + t.is(ResourceCollectorLog.verbose.callCount, 2); + t.deepEqual(ResourceCollectorLog.verbose.getCall(0).args, + ["\tMissing submodule my/app/view/Main.view.xml included by my/app/bundle.js"]); + t.deepEqual(ResourceCollectorLog.verbose.getCall(1).args, + [" Configured external resources filters (resources outside the namespace): (none)"]); + + t.is(resourcesJson.length, 1, "One resources.json should be returned"); + const myAppResourcesJson = resourcesJson[0]; + t.is(myAppResourcesJson.getPath(), "/resources/my/app/resources.json"); + const myAppResourcesJsonContent = await myAppResourcesJson.getString(); + t.is(myAppResourcesJsonContent, `{ + "_version": "1.1.0", + "resources": [ + { + "name": "bundle.js", + "module": "my/app/bundle.js", + "size": 93, + "merged": true, + "included": [ + "my/app/view/Main.view.xml" + ] + }, + { + "name": "manifest.json", + "module": "my/app/manifest.json", + "size": 27 + }, + { + "name": "resources.json", + "size": 338 + } + ] +}`); +}); + +test.serial("Bundles with subModules should not cause analyzing the same module multiple times", async (t) => { + const { + sinon, resourceListCreator, resourceListCreatorLog, ResourceCollectorLog, XMLTemplateAnalyzerAnalyzeViewSpy + } = t.context; + + const myAppManifestJsonResource = createResource({ + path: "/resources/my/app/manifest.json", + string: JSON.stringify({"sap.app": {"id": "my.app"}}) + }); + + const xmlView1content = ""; + const myAppViewResource1 = createResource({ + path: "/resources/my/app/View1.view.xml", + string: xmlView1content + }); + // Delay getBuffer to cause situation where the xml resource hasn't been analyzed + // when processing the bundle subModules. + // This should not lead to multiple analysis of the same resource. Instead it should + // wait for the pending analysis to be finished + sinon.stub(myAppViewResource1, "getBuffer").callsFake(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(Buffer.from(xmlView1content)); + }, 10); + }); + }); + + const myAppBundleResource1 = createResource({ + path: "/resources/my/app/bundle1.js", + string: `//@ui5-bundle my/app/bundle1.js +sap.ui.require.preload({ + "my/app/View1.view.xml": '${xmlView1content}' +}); +` + }); + const myAppBundleResource2 = createResource({ + path: "/resources/my/app/bundle2.js", + string: `//@ui5-bundle my/app/bundle2.js +sap.ui.require.preload({ + "my/app/View1.view.xml": '${xmlView1content}' +}); +` + }); + + const resourcesJson = await resourceListCreator({ + resources: [myAppManifestJsonResource, myAppBundleResource1, myAppBundleResource2, myAppViewResource1] + }); + + t.is(resourceListCreatorLog.error.callCount, 0); + t.is(resourceListCreatorLog.verbose.callCount, 2); + t.deepEqual(resourceListCreatorLog.verbose.getCall(0).args, + ["\tFound 4 resources"]); + t.deepEqual(resourceListCreatorLog.verbose.getCall(1).args, + ["\tWriting 'my/app/resources.json'"]); + + t.is(ResourceCollectorLog.error.callCount, 0); + t.is(ResourceCollectorLog.warn.callCount, 0); + t.is(ResourceCollectorLog.verbose.callCount, 1); + t.deepEqual(ResourceCollectorLog.verbose.getCall(0).args, + [" Configured external resources filters (resources outside the namespace): (none)"]); + + // XMLTemplateAnalyzer should only be called once, which means that the view was only analyzed once + t.is(XMLTemplateAnalyzerAnalyzeViewSpy.callCount, 1); + t.is(XMLTemplateAnalyzerAnalyzeViewSpy.getCall(0).args[1]._name, + "my/app/View1.view.xml"); + + t.is(resourcesJson.length, 1, "One resources.json should be returned"); + const myAppResourcesJson = resourcesJson[0]; + t.is(myAppResourcesJson.getPath(), "/resources/my/app/resources.json"); + const myAppResourcesJsonContent = await myAppResourcesJson.getString(); + t.is(myAppResourcesJsonContent, `{ + "_version": "1.1.0", + "resources": [ + { + "name": "View1.view.xml", + "module": "my/app/View1.view.xml", + "size": 49 + }, + { + "name": "bundle1.js", + "module": "my/app/bundle1.js", + "size": 139, + "merged": true, + "included": [ + "my/app/View1.view.xml" + ] + }, + { + "name": "bundle2.js", + "module": "my/app/bundle2.js", + "size": 139, + "merged": true, + "included": [ + "my/app/View1.view.xml" + ] + }, + { + "name": "manifest.json", + "module": "my/app/manifest.json", + "size": 27 + }, + { + "name": "resources.json", + "size": 580 + } + ] +}`); +}); + +test.serial("Bundle", async (t) => { + const {resourceListCreator, resourceListCreatorLog, ResourceCollectorLog} = t.context; + + const myAppManifestJsonResource = createResource({ + path: "/resources/my/app/manifest.json", + string: JSON.stringify({"sap.app": {"id": "my.app"}}) + }); + + const myAppBundleResource = createResource({ + path: "/resources/my/app/bundle.js", + string: `//@ui5-bundle my/app/bundle.js +sap.ui.require.preload({ + "my/app/module1.js": '', + "my/app/module2.js": '' +}); +` + }); + + const module1Resource = createResource({ + path: "/resources/my/app/module1.js", + string: `sap.ui.define(['dep1'], function() { + return function(x) { + if (x === true) { + sap.ui.require(["dep2"]); + } + } + })` + }); + + const module2Resource = createResource({ + path: "/resources/my/app/module2.js", + string: `sap.ui.define(['dep2'], function() { + return function(x) { + if (x === true) { + sap.ui.require(["dep1", "dep3"]); + } + } + })` + }); + + const resourcesJson = await resourceListCreator({ + resources: [myAppManifestJsonResource, myAppBundleResource, module1Resource, module2Resource], + }); + + t.is(resourceListCreatorLog.error.callCount, 0); + t.is(resourceListCreatorLog.verbose.callCount, 2); + t.deepEqual(resourceListCreatorLog.verbose.getCall(0).args, + ["\tFound 4 resources"]); + t.deepEqual(resourceListCreatorLog.verbose.getCall(1).args, + ["\tWriting 'my/app/resources.json'"]); + + t.is(ResourceCollectorLog.error.callCount, 0); + t.is(ResourceCollectorLog.warn.callCount, 0); + t.is(ResourceCollectorLog.verbose.callCount, 1); + t.deepEqual(ResourceCollectorLog.verbose.getCall(0).args, + [" Configured external resources filters (resources outside the namespace): (none)"]); + + t.is(resourcesJson.length, 1, "One resources.json should be returned"); + const myAppResourcesJson = resourcesJson[0]; + t.is(myAppResourcesJson.getPath(), "/resources/my/app/resources.json"); + const myAppResourcesJsonContent = await myAppResourcesJson.getString(); + t.is(myAppResourcesJsonContent, `{ + "_version": "1.1.0", + "resources": [ + { + "name": "bundle.js", + "module": "my/app/bundle.js", + "size": 111, + "merged": true, + "required": [ + "dep1.js", + "dep2.js" + ], + "condRequired": [ + "dep3.js" + ], + "included": [ + "my/app/module1.js", + "my/app/module2.js" + ] + }, + { + "name": "manifest.json", + "module": "my/app/manifest.json", + "size": 27 + }, + { + "name": "module1.js", + "module": "my/app/module1.js", + "size": 129, + "required": [ + "dep1.js" + ], + "condRequired": [ + "dep2.js" + ] + }, + { + "name": "module2.js", + "module": "my/app/module2.js", + "size": 137, + "required": [ + "dep2.js" + ], + "condRequired": [ + "dep1.js", + "dep3.js" + ] + }, + { + "name": "resources.json", + "size": 786 + } + ] +}`); +}); diff --git a/packages/builder/test/lib/processors/stringReplacer.js b/packages/builder/test/lib/processors/stringReplacer.js new file mode 100644 index 00000000000..6e1e4d2894c --- /dev/null +++ b/packages/builder/test/lib/processors/stringReplacer.js @@ -0,0 +1,166 @@ +import test from "ava"; +import _sinon from "sinon"; +import stringReplacer from "../../../lib/processors/stringReplacer.js"; + +test.beforeEach((t) => { + t.context.sinon = _sinon.createSandbox(); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("Replace using string pattern", async (t) => { + const {sinon} = t.context; + const input = `foo bar foo`; + const expected = `foo foo foo`; + + const resource = { + getString: sinon.stub().resolves(input), + setString: sinon.stub() + }; + + const processedResources = await stringReplacer({ + resources: [resource], + options: { + pattern: "bar", + replacement: "foo" + } + }); + + t.is(processedResources.length, 1, "Returned one resource"); + t.is(processedResources[0], resource, "Input resource is returned"); + t.is(resource.setString.callCount, 1, "Resource#setString got called once"); + t.is(resource.setString.firstCall.firstArg, expected, "Resource#setString got called with expected argument"); +}); + +test.serial("No replacement", async (t) => { + const {sinon} = t.context; + const input = `foo foo foo`; + + const resource = { + getString: sinon.stub().resolves(input), + setString: sinon.stub() + }; + + const processedResources = await stringReplacer({ + resources: [resource], + options: { + pattern: "bar", + replacement: "foo" + } + }); + + t.is(processedResources.length, 1, "Returned one resource"); + t.is(processedResources[0], resource, "Input resource is returned"); + t.is(resource.setString.callCount, 0, "Resource#setString did not get called"); +}); + +test.serial("Replace using regular expression", async (t) => { + const {sinon} = t.context; + const input = `foo BAR foo`; + const expected = `foo foo foo`; + + const resource = { + getString: sinon.stub().resolves(input), + setString: sinon.stub() + }; + + const processedResources = await stringReplacer({ + resources: [resource], + options: { + pattern: /bar/ig, + replacement: "foo" + } + }); + + t.is(processedResources.length, 1, "Returned one resource"); + t.is(processedResources[0], resource, "Input resource is returned"); + t.is(resource.setString.callCount, 1, "Resource#setString got called once"); + t.is(resource.setString.firstCall.firstArg, expected, "Resource#setString got called with expected argument"); +}); + +test.serial("Regular expression requires global flag", async (t) => { + const {sinon} = t.context; + const input = `foo bar foo`; + + const resource = { + getString: sinon.stub().resolves(input), + setString: sinon.stub() + }; + + await t.throwsAsync(stringReplacer({ + resources: [resource], + options: { + pattern: /bar/i, + replacement: "foo" + } + }), { + message: "String.prototype.replaceAll called with a non-global RegExp argument" + }, "Threw with expected error message"); +}); + +test.serial("Replaces string pattern with UTF8 characters", async (t) => { + const {sinon} = t.context; + const input = `早安`; + const expected = `午安`; + + const resource = { + getString: sinon.stub().resolves(input), + setString: sinon.stub() + }; + + const processedResources = await stringReplacer({ + resources: [resource], + options: { + pattern: /早/g, + replacement: "午" + } + }); + + t.is(processedResources.length, 1, "Returned one resource"); + t.is(processedResources[0], resource, "Input resource is returned"); + t.is(resource.setString.callCount, 1, "Resource#setString got called once"); + t.is(resource.setString.firstCall.firstArg, expected, "Resource#setString got called with expected argument"); +}); + +test.serial("Process multiple resources", async (t) => { + const {sinon} = t.context; + + const resourceA = { + getString: sinon.stub().resolves("Resource A"), + setString: sinon.stub() + }; + const resourceB = { + getString: sinon.stub().resolves("Resource B"), + setString: sinon.stub() + }; + const resourceC = { + getString: sinon.stub().resolves("Resource 三"), + setString: sinon.stub() + }; + const resourceD = { + getString: sinon.stub().resolves("Resource D"), + setString: sinon.stub() + }; + + const processedResources = await stringReplacer({ + resources: [resourceA, resourceB, resourceC, resourceD], + options: { + pattern: /[B-D]/g, + replacement: "💎" + } + }); + + t.is(processedResources.length, 4, "Returned all four resources"); + t.is(processedResources[0], resourceA, "Input resourceA is returned"); + t.is(processedResources[1], resourceB, "Input resourceB is returned"); + t.is(processedResources[2], resourceC, "Input resourceC is returned"); + t.is(processedResources[3], resourceD, "Input resourceD is returned"); + t.is(resourceA.setString.callCount, 0, "resourceA#setString did not get called"); + t.is(resourceB.setString.callCount, 1, "resourceB#setString got called once"); + t.is(resourceB.setString.firstCall.firstArg, "Resource 💎", "resourceB#setString got called with expected argument"); + t.is(resourceC.setString.callCount, 0, "resourceC#setString did not get called"); + t.is(resourceD.setString.callCount, 1, "resourceD#setString got called once"); + t.is(resourceD.setString.firstCall.firstArg, "Resource 💎", "resourceD#setString got called with expected argument"); +}); diff --git a/packages/builder/test/lib/processors/themeBuilder.js b/packages/builder/test/lib/processors/themeBuilder.js new file mode 100644 index 00000000000..5e5c2242145 --- /dev/null +++ b/packages/builder/test/lib/processors/themeBuilder.js @@ -0,0 +1,293 @@ +import test from "ava"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import fsInterface from "@ui5/fs/fsInterface"; + +import themeBuilderProcessor from "../../../lib/processors/themeBuilder.js"; +import {ThemeBuilder} from "../../../lib/processors/themeBuilder.js"; + +async function prepareResources({library} = {}) { + const input = +`@someColor: black; +.someClass { + color: @someColor; + padding: 1px 2px 3px 4px; +}`; + + const memoryAdapter = createAdapter({ + virBasePath: "/" + }); + + let lessFilePath; + if (library === false) { + lessFilePath = "/resources/foo.less"; + } else { + lessFilePath = "/resources/sap/ui/foo/themes/base/library.source.less"; + } + + const resource = createResource({ + path: lessFilePath, + string: input + }); + + await memoryAdapter.write(resource); + + return { + resource, + memoryAdapter + }; +} + +function getExpectedResults({compress, library, cssVariables}) { + const result = {}; + if (compress) { + result.css = +`.someClass{color:#000;padding:1px 2px 3px 4px}`; + + result.cssRtl = +`.someClass{color:#000;padding:1px 4px 3px 2px}`; + result.json = `{"someColor":"#000"}`; + } else { + result.css = +`.someClass { + color: #000000; + padding: 1px 2px 3px 4px; +} +`; + + result.cssRtl = +`.someClass { + color: #000000; + padding: 1px 4px 3px 2px; +} +`; + + result.json = +`{ + "someColor": "#000000" +}`; + } + + if (library !== false) { + result.css += +` +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.foo{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23` + +`${compress ? "000" : "000000"}%22%7D')} +`; + result.cssRtl += +` +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.foo{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23` + +`${compress ? "000" : "000000"}%22%7D')} +`; + } + + if (cssVariables) { + result.cssVariablesSource = +`@someColor: #000000; + +:root { +--someColor: @someColor; +} +`; + result.cssVariables = +`:root { + --someColor: #000000; +} + +/* Inline theming parameters */ +#sap-ui-theme-sap\\.ui\\.foo{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000000%22%7D')} +`; + result.cssSkeleton = +`.someClass { + color: var(--someColor); + padding: 1px 2px 3px 4px; +} +`; + result.cssSkeletonRtl = +`.someClass { + color: var(--someColor); + padding: 1px 4px 3px 2px; +} +`; + } + + return result; +} + +test("Processor: Builds a less file (default options)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilderProcessor({ + resources: [resource], + fs: fsInterface(memoryAdapter) + }); + + const expected = getExpectedResults({compress: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); + + t.is(cssResource.getPath(), "/resources/sap/ui/foo/themes/base/library.css", "CSS resource path should be correct"); + t.is(cssRtlResource.getPath(), "/resources/sap/ui/foo/themes/base/library-RTL.css", + "Right-to-left CSS resource path should be correct"); + t.is(jsonResource.getPath(), "/resources/sap/ui/foo/themes/base/library-parameters.json", + "JSON resource path should be correct"); +}); + +test("Processor: Builds a less file (compress = true)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilderProcessor({ + resources: [resource], + fs: fsInterface(memoryAdapter), + options: { + compress: true + } + }); + + const expected = getExpectedResults({compress: true}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("Processor: Builds a less file (compress = false)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilderProcessor({ + resources: [resource], + fs: fsInterface(memoryAdapter), + options: { + compress: false + } + }); + + const expected = getExpectedResults({compress: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("Processor: Builds a less file (no library)", async (t) => { + const {resource, memoryAdapter} = await prepareResources({library: false}); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilderProcessor({ + resources: [resource], + fs: fsInterface(memoryAdapter), + options: { + compress: false + } + }); + + const expected = getExpectedResults({compress: false, library: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("ThemeBuilder: Builds a less file", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const themeBuilder = new ThemeBuilder({fs: fsInterface(memoryAdapter)}); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilder.build([resource]); + + const expected = getExpectedResults({compress: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("ThemeBuilder: Builds a less file (compress = true)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const themeBuilder = new ThemeBuilder({fs: fsInterface(memoryAdapter)}); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilder.build([resource], { + compress: true + }); + + const expected = getExpectedResults({compress: true}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("ThemeBuilder: Builds a less file (compress = false)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const themeBuilder = new ThemeBuilder({fs: fsInterface(memoryAdapter)}); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilder.build([resource], { + compress: false + }); + + const expected = getExpectedResults({compress: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("ThemeBuilder: Builds a less file (no library)", async (t) => { + const {resource, memoryAdapter} = await prepareResources({library: false}); + + const themeBuilder = new ThemeBuilder({fs: fsInterface(memoryAdapter)}); + + const [cssResource, cssRtlResource, jsonResource] = await themeBuilder.build([resource], { + compress: false + }); + + const expected = getExpectedResults({compress: false, library: false}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); +}); + +test("Processor: Builds a less file (cssVariables = true)", async (t) => { + const {resource, memoryAdapter} = await prepareResources(); + + const [ + cssResource, + cssRtlResource, + jsonResource, + cssVariablesSourceResource, + cssVariablesResource, + cssSkeletonResource, + cssSkeletonRtlResource + ] = await themeBuilderProcessor({ + resources: [resource], + fs: fsInterface(memoryAdapter), + options: { + cssVariables: true + } + }); + + const expected = getExpectedResults({cssVariables: true}); + t.is(await cssResource.getString(), expected.css, "CSS should be correct"); + t.is(await cssRtlResource.getString(), expected.cssRtl, "Right-to-left CSS should be correct"); + t.is(await jsonResource.getString(), expected.json, "JSON should be correct"); + t.is(await cssVariablesSourceResource.getString(), expected.cssVariablesSource, + "CSS Variables source should be correct"); + t.is(await cssVariablesResource.getString(), expected.cssVariables, + "CSS Variables should be correct"); + t.is(await cssSkeletonResource.getString(), expected.cssSkeleton, + "Skeleton CSS should be correct"); + t.is(await cssSkeletonRtlResource.getString(), expected.cssSkeletonRtl, + "Right-to-left skeleton CSS should be correct"); + + t.is(cssResource.getPath(), "/resources/sap/ui/foo/themes/base/library.css", + "CSS resource path should be correct"); + t.is(cssRtlResource.getPath(), "/resources/sap/ui/foo/themes/base/library-RTL.css", + "Right-to-left CSS resource path should be correct"); + t.is(jsonResource.getPath(), "/resources/sap/ui/foo/themes/base/library-parameters.json", + "JSON resource path should be correct"); + t.is(cssVariablesSourceResource.getPath(), "/resources/sap/ui/foo/themes/base/css_variables.source.less", + "CSS Variables source path should be correct"); + t.is(cssVariablesResource.getPath(), "/resources/sap/ui/foo/themes/base/css_variables.css", + "CSS Variables resource path should be correct"); + t.is(cssSkeletonResource.getPath(), "/resources/sap/ui/foo/themes/base/library_skeleton.css", + "Skeleton CSS resource path should be correct"); + t.is(cssSkeletonRtlResource.getPath(), "/resources/sap/ui/foo/themes/base/library_skeleton-RTL.css", + "Right-to-left skeleton CSS resource path should be correct"); +}); diff --git a/packages/builder/test/lib/processors/versionInfoGenerator.js b/packages/builder/test/lib/processors/versionInfoGenerator.js new file mode 100644 index 00000000000..ff88226818d --- /dev/null +++ b/packages/builder/test/lib/processors/versionInfoGenerator.js @@ -0,0 +1,515 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.warnLogStub = sinon.stub(); + t.context.infoLogStub = sinon.stub(); + t.context.verboseLogStub = sinon.stub(); + t.context.sillyLogStub = sinon.stub(); + t.context.versionInfoGenerator = await esmock("../../../lib/processors/versionInfoGenerator.js", { + "@ui5/logger": { + getLogger: () => ({ + warn: t.context.warnLogStub, + info: t.context.infoLogStub, + verbose: t.context.verboseLogStub, + silly: t.context.sillyLogStub, + isLevelEnabled: () => true + }) + } + }); +}); + +test("versionInfoGenerator missing parameters", async (t) => { + const {versionInfoGenerator} = t.context; + const error = await t.throwsAsync(versionInfoGenerator({options: {}})); + t.is(error.message, "[versionInfoGenerator]: Missing options parameters"); +}); + +const assertVersionInfoContent = (t, oExpectedVersionInfo, sActualContent) => { + const currentVersionInfo = JSON.parse(sActualContent); + + t.is(currentVersionInfo.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + + delete currentVersionInfo.buildTimestamp; // removing to allow deep comparison + currentVersionInfo.libraries.forEach((lib) => { + t.is(lib.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + delete lib.buildTimestamp; // removing to allow deep comparison + }); + + + t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); +}; + +test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => { + const {versionInfoGenerator} = t.context; + const versionInfos = await versionInfoGenerator({options: { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: []}}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [] + }; + assertVersionInfoContent(t, oExpected, result); +}); + + +test.serial("versionInfoGenerator simple library infos", async (t) => { + const {versionInfoGenerator} = t.context; + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + {name: "my.lib", version: "1.2.3"} + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "my.lib", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.verboseLogStub.callCount, 1); + t.is(t.context.verboseLogStub.getCall(0).args[0], + "Cannot add meta information for library 'my.lib'. The manifest.json file cannot be found"); +}); + +test.serial("versionInfoGenerator manifest without libs", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84" + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with dependencies", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "my.dep": { + "minVersion": "1.84.0", + "lazy": false + } + } + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + const myDepManifest = { + getPath: () => { + return "/resources/my/dep/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "my.dep", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const myDep = {name: "my.dep", version: "1.2.3", libraryManifest: myDepManifest}; + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA, myDep + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "my.dep": {} + } + } + } + }, + { + "name": "my.dep", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with dependencies; w/o minVersion and minUI5Version", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "", + "libs": { + "my.dep": { + "minVersion": "", + "lazy": false + } + } + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + const options = {rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [libA]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "my.dep": {} + } + } + } + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 1); + t.is(t.context.infoLogStub.getCall(0).args[0], + "Cannot find dependency 'my.dep' " + + "defined in the manifest.json or .library file of project 'lib.a'. " + + "This might prevent some UI5 runtime performance optimizations from taking effect. " + + "Please double check your project's dependency configuration."); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with embeds", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": ["sub"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const subManifest = { + getPath: () => { + return "/resources/lib/a/sub/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a.sub", + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest, embeddedManifests: [subManifest]}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ], + "components": { + "lib.a.sub": { + "hasOwnPreload": true, + "library": "lib.a" + } + } + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with no embeds", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": { + "my.dep": { + "minVersion": "1.84.0", + "lazy": false + } + } + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest}; + const myDepManifest = { + getPath: () => { + return "/resources/my/dep/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "my.dep" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const myDep = {name: "my.dep", version: "1.2.3", libraryManifest: myDepManifest}; + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA, myDep + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "my.dep": {} + } + } + } + }, + { + "name": "my.dep", + "version": "1.2.3", + "scmRevision": "" + } + ] + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); + +test.serial("versionInfoGenerator library infos with embeds and embeddedBy (hasOwnPreload)", async (t) => { + const {versionInfoGenerator} = t.context; + const libAManifest = { + getPath: () => { + return "/resources/lib/a/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a", + "embeds": ["sub"] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const subManifest = { + getPath: () => { + return "/resources/lib/a/sub/manifest.json"; + }, + getString: async () => { + return JSON.stringify({ + "sap.app": { + "id": "lib.a.sub", + "embeds": [], + "embeddedBy": "../" + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }); + } + }; + const libA = {name: "lib.a", version: "1.2.3", libraryManifest: libAManifest, embeddedManifests: [subManifest]}; + + const options = { + rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [ + libA + ]}; + const versionInfos = await versionInfoGenerator({options}); + + const resource = versionInfos[0]; + const result = await resource.getString(); + + const oExpected = { + "name": "myname", + "version": "1.33.7", + "scmRevision": "", + "libraries": [ + { + "name": "lib.a", + "version": "1.2.3", + "scmRevision": "" + } + ], + "components": { + "lib.a.sub": { + "library": "lib.a" + } + } + }; + assertVersionInfoContent(t, oExpected, result); + t.is(t.context.infoLogStub.callCount, 0); + t.is(t.context.warnLogStub.callCount, 0); +}); diff --git a/packages/builder/test/lib/tasks/buildThemes.integration.js b/packages/builder/test/lib/tasks/buildThemes.integration.js new file mode 100644 index 00000000000..23434f5e624 --- /dev/null +++ b/packages/builder/test/lib/tasks/buildThemes.integration.js @@ -0,0 +1,156 @@ +import test from "ava"; +import buildThemes from "../../../lib/tasks/buildThemes.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +test("integration: simple", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = createAdapter({ + virBasePath: "/" + }); + + const content = +`@deepSea: #123456; +.fluffyHammer { + color: @deepSea; + padding: 1px 2px 3px 4px; +}`; + const cssExpected = +`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px} +/* Inline theming parameters */ +#sap-ui-theme-super\\.duper\\.looper` + +`{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} +`; + const cssRtlExpected = +`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px} +/* Inline theming parameters */ +#sap-ui-theme-super\\.duper\\.looper` + +`{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} +`; + const parametersExpected = +`{"deepSea":"#123456"}`; + const lessPath = "/resources/super/duper/looper/themes/brightlight/library.source.less"; + const cssPath = "/resources/super/duper/looper/themes/brightlight/library.css"; + const cssRtlPath = "/resources/super/duper/looper/themes/brightlight/library-RTL.css"; + const parametersPath = "/resources/super/duper/looper/themes/brightlight/library-parameters.json"; + + const resource = createResource({ + path: lessPath, + string: content + }); + await reader.write(resource); + await buildThemes({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + inputPattern: "/resources/super/duper/looper/themes/**/library.source.less" + } + }); + + const [cssResource, cssRtlResource, parametersResource] = await Promise.all([ + writer.byPath(cssPath), + writer.byPath(cssRtlPath), + writer.byPath(parametersPath) + ]); + + t.truthy(cssResource, "CSS resource has been created"); + t.truthy(cssRtlResource, "CSS right-to-left resource has been created"); + t.truthy(parametersResource, "Parameters JSON resource has been created"); + + const [cssBuffer, cssRtlBuffer, parametersBuffer] = await Promise.all([ + cssResource.getBuffer(), + cssRtlResource.getBuffer(), + parametersResource.getBuffer() + ]); + + t.deepEqual(cssBuffer.toString(), cssExpected, "Correct CSS content"); + t.deepEqual(cssRtlBuffer.toString(), cssRtlExpected, "Correct CSS right-to-left content"); + t.deepEqual(parametersBuffer.toString(), parametersExpected, "Correct parameters JSON content"); +}); +test("integration: imports", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = createAdapter({ + virBasePath: "/" + }); + const lessContent = +`@import "variables.less"; +.fluffyHammer { + color: @deepSea; + padding: 1px 2px 3px 4px; +}`; + const lessVariablesContent = +"@deepSea: #123456;"; + const cssExpected = +`.fluffyHammer{color:#123456;padding:1px 2px 3px 4px} +/* Inline theming parameters */ +#sap-ui-theme-super\\.duper\\.looper` + +`{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} +`; + const cssRtlExpected = +`.fluffyHammer{color:#123456;padding:1px 4px 3px 2px} +/* Inline theming parameters */ +#sap-ui-theme-super\\.duper\\.looper` + +`{background-image:url('data:text/plain;utf-8,%7B%22deepSea%22%3A%22%23123456%22%7D')} +`; + const parametersExpected = +`{"deepSea":"#123456"}`; + const lessPath = "/resources/super/duper/looper/themes/brightlight/library.source.less"; + const lessVariablesPath = "/resources/super/duper/looper/themes/brightlight/variables.less"; + const cssPath = "/resources/super/duper/looper/themes/brightlight/library.css"; + const cssRtlPath = "/resources/super/duper/looper/themes/brightlight/library-RTL.css"; + const parametersPath = "/resources/super/duper/looper/themes/brightlight/library-parameters.json"; + + const lessResource = createResource({ + path: lessPath, + string: lessContent + }); + + const lessVariablesResource = createResource({ + path: lessVariablesPath, + string: lessVariablesContent + }); + + await Promise.all([lessResource, lessVariablesResource].map((resource) => { + return reader.write(resource); + })); + + await buildThemes({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + inputPattern: "/resources/super/duper/looper/themes/**/library.source.less" + } + }); + + const [cssResource, cssRtlResource, parametersResource] = await Promise.all([ + writer.byPath(cssPath), + writer.byPath(cssRtlPath), + writer.byPath(parametersPath) + ]); + + t.truthy(cssResource, "CSS resource has been created"); + t.truthy(cssRtlResource, "CSS right-to-left resource has been created"); + t.truthy(parametersResource, "Parameters JSON resource has been created"); + + const [cssBuffer, cssRtlBuffer, parametersBuffer] = await Promise.all([ + cssResource.getBuffer(), + cssRtlResource.getBuffer(), + parametersResource.getBuffer() + ]); + + t.deepEqual(cssBuffer.toString(), cssExpected, "Correct CSS content"); + t.deepEqual(cssRtlBuffer.toString(), cssRtlExpected, "Correct CSS right-to-left content"); + t.deepEqual(parametersBuffer.toString(), parametersExpected, "Correct parameters JSON content"); +}); diff --git a/packages/builder/test/lib/tasks/buildThemes.js b/packages/builder/test/lib/tasks/buildThemes.js new file mode 100644 index 00000000000..2cf3de924c0 --- /dev/null +++ b/packages/builder/test/lib/tasks/buildThemes.js @@ -0,0 +1,620 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; +import {deserializeResources} from "../../../lib/processors/themeBuilderWorker.js"; +let buildThemes; + +test.before(async () => { + // Enable verbose logging to also cover verbose logging code + const {setLogLevel} = await import("@ui5/logger"); + setLogLevel("verbose"); +}); + +test.beforeEach(async (t) => { + // Stubbing processors/themeBuilder + t.context.themeBuilderStub = sinon.stub(); + t.context.fsInterfaceStub = sinon.stub(); + t.context.fsInterfaceStub.returns({}); + + t.context.ReaderCollectionPrioritizedStub = sinon.stub(); + t.context.comboByGlob = sinon.stub().resolves([]); + t.context.ReaderCollectionPrioritizedStub.returns({byGlob: t.context.comboByGlob}); + + buildThemes = await esmock.p("../../../lib/tasks/buildThemes.js", { + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "@ui5/fs/ReaderCollectionPrioritized": t.context.ReaderCollectionPrioritizedStub, + "../../../lib/processors/themeBuilder.js": t.context.themeBuilderStub + }); +}); + +test.afterEach.always(() => { + esmock.purge(buildThemes); + sinon.restore(); +}); + +test.serial("buildThemes", async (t) => { + t.plan(6); + + const lessResource = {}; + + const workspace = { + byGlob: async (globPattern) => { + if (globPattern === "/resources/test/library.source.less") { + return [lessResource]; + } else { + return []; + } + }, + write: sinon.stub() + }; + + const cssResource = {}; + const cssRtlResource = {}; + const jsonParametersResource = {}; + + t.context.themeBuilderStub.returns([ + cssResource, + cssRtlResource, + jsonParametersResource + ]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.demo.app", + inputPattern: "/resources/test/library.source.less" + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [lessResource], + fs: {}, + options: { + compress: true, // default + cssVariables: false // default + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 3, + "workspace.write should be called 3 times"); + t.true(workspace.write.calledWithExactly(cssResource)); + t.true(workspace.write.calledWithExactly(cssRtlResource)); + t.true(workspace.write.calledWithExactly(jsonParametersResource)); +}); + + +test.serial("buildThemes (compress = false)", async (t) => { + t.plan(6); + + const lessResource = {}; + + const workspace = { + byGlob: async (globPattern) => { + if (globPattern === "/resources/test/library.source.less") { + return [lessResource]; + } else { + return []; + } + }, + write: sinon.stub() + }; + + const cssResource = {}; + const cssRtlResource = {}; + const jsonParametersResource = {}; + + t.context.themeBuilderStub.returns([ + cssResource, + cssRtlResource, + jsonParametersResource + ]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.demo.app", + inputPattern: "/resources/test/library.source.less", + compress: false + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [lessResource], + fs: {}, + options: { + compress: false, + cssVariables: false + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 3, + "workspace.write should be called 3 times"); + t.true(workspace.write.calledWithExactly(cssResource)); + t.true(workspace.write.calledWithExactly(cssRtlResource)); + t.true(workspace.write.calledWithExactly(jsonParametersResource)); +}); + +test.serial("buildThemes (cssVariables = true)", async (t) => { + t.plan(10); + + const lessResource = {}; + + const workspace = { + byGlob: async (globPattern) => { + if (globPattern === "/resources/test/library.source.less") { + return [lessResource]; + } else { + return []; + } + }, + write: sinon.stub() + }; + + const cssResource = {}; + const cssRtlResource = {}; + const jsonParametersResource = {}; + const cssVariablesSourceResource = {}; + const cssVariablesResource = {}; + const cssSkeletonResource = {}; + const cssSkeletonRtlResource = {}; + + t.context.themeBuilderStub.returns([ + cssResource, + cssRtlResource, + jsonParametersResource, + cssVariablesSourceResource, + cssVariablesResource, + cssSkeletonResource, + cssSkeletonRtlResource + ]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.demo.app", + inputPattern: "/resources/test/library.source.less", + cssVariables: true + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [lessResource], + fs: {}, + options: { + compress: true, + cssVariables: true + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 7, + "workspace.write should be called 7 times"); + t.true(workspace.write.calledWithExactly(cssResource)); + t.true(workspace.write.calledWithExactly(cssRtlResource)); + t.true(workspace.write.calledWithExactly(jsonParametersResource)); + t.true(workspace.write.calledWithExactly(cssVariablesSourceResource)); + t.true(workspace.write.calledWithExactly(cssVariablesResource)); + t.true(workspace.write.calledWithExactly(cssSkeletonResource)); + t.true(workspace.write.calledWithExactly(cssSkeletonRtlResource)); +}); + +test.serial("buildThemes (filtering libraries)", async (t) => { + t.plan(3); + + const lessResources = { + "sap/ui/lib1/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme1/library.source.less") + }, + "sap/ui/lib2/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib2/themes/theme1/library.source.less") + }, + "sap/ui/lib3/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/themes/theme1/library.source.less") + } + }; + + const dotLibraryResources = { + "sap/ui/lib1/.library": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/.library") + }, + "sap/ui/lib1/library.js": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/library.js") + }, + "sap/ui/lib3/library.js": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/library.js") + } + }; + + const workspaceByGlob = sinon.stub(); + const workspace = { + byGlob: workspaceByGlob, + write: sinon.stub() + }; + + workspaceByGlob + .withArgs("/resources/**/themes/*/library.source.less").resolves([ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib2/themes/theme1/library.source.less"], + lessResources["sap/ui/lib3/themes/theme1/library.source.less"] + ]); + + t.context.comboByGlob + .withArgs("/resources/**/(*.library|library.js)").resolves([ + dotLibraryResources["sap/ui/lib1/.library"], + dotLibraryResources["sap/ui/lib1/library.js"], + dotLibraryResources["sap/ui/lib3/library.js"] + ]); + + t.context.themeBuilderStub.returns([{}]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.test.lib1", + inputPattern: "/resources/**/themes/*/library.source.less", + librariesPattern: "/resources/**/(*.library|library.js)" + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib3/themes/theme1/library.source.less"] + ], + fs: {}, + options: { + compress: true, + cssVariables: false + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 1, + "workspace.write should be called once"); +}); + +test.serial("buildThemes (filtering themes)", async (t) => { + t.plan(3); + + const lessResources = { + "sap/ui/lib1/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme1/library.source.less") + }, + "sap/ui/lib1/themes/theme2/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme2/library.source.less") + }, + "sap/ui/lib1/themes/theme3/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme3/library.source.less") + } + }; + + const baseThemes = { + "sap/ui/core/themes/theme1/": { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/theme1/"), + getStatInfo: () => { + return {isDirectory: () => true}; + } + }, + "sap/ui/core/themes/theme3/": { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/theme3/"), + getStatInfo: () => { + return {isDirectory: () => true}; + } + } + }; + + const workspaceByGlob = sinon.stub(); + const workspace = { + byGlob: workspaceByGlob, + write: sinon.stub() + }; + + workspaceByGlob + .withArgs("/resources/**/themes/*/library.source.less").resolves([ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib1/themes/theme2/library.source.less"], + lessResources["sap/ui/lib1/themes/theme3/library.source.less"] + ]); + + t.context.comboByGlob + .withArgs("/resources/sap/ui/core/themes/*", {nodir: false}).resolves([ + baseThemes["sap/ui/core/themes/theme1/"], + baseThemes["sap/ui/core/themes/theme3/"] + ]); + + t.context.themeBuilderStub.returns([{}]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.test.lib1", + inputPattern: "/resources/**/themes/*/library.source.less", + themesPattern: "/resources/sap/ui/core/themes/*" + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib1/themes/theme3/library.source.less"] + ], + fs: {}, + options: { + compress: true, + cssVariables: false + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 1, + "workspace.write should be called once"); +}); + +test.serial("buildThemes (filtering libraries + themes)", async (t) => { + t.plan(3); + + const lessResources = { + "sap/ui/lib1/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme1/library.source.less") + }, + "sap/ui/lib1/themes/theme2/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme2/library.source.less") + }, + "sap/ui/lib1/themes/theme3/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/themes/theme3/library.source.less") + }, + "sap/ui/lib2/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib2/themes/theme1/library.source.less") + }, + "sap/ui/lib2/themes/theme2/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib2/themes/theme2/library.source.less") + }, + "sap/ui/lib2/themes/theme3/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib2/themes/theme3/library.source.less") + }, + "sap/ui/lib3/themes/theme1/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/themes/theme1/library.source.less") + }, + "sap/ui/lib3/themes/theme2/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/themes/theme2/library.source.less") + }, + "sap/ui/lib3/themes/theme3/library.source.less": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/themes/theme3/library.source.less") + } + }; + + const dotLibraryResources = { + "sap/ui/lib1/.library": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/.library") + }, + "sap/ui/lib1/library.js": { + getPath: sinon.stub().returns("/resources/sap/ui/lib1/library.js") + }, + "sap/ui/lib3/library.js": { + getPath: sinon.stub().returns("/resources/sap/ui/lib3/library.js") + } + }; + + const baseThemes = { + "sap/ui/core/themes/theme1/": { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/theme1/"), + getStatInfo: () => { + return {isDirectory: () => true}; + } + }, + "sap/ui/core/themes/theme3/": { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/theme3/"), + getStatInfo: () => { + return {isDirectory: () => true}; + } + } + }; + + const workspaceByGlob = sinon.stub(); + const workspace = { + byGlob: workspaceByGlob, + write: sinon.stub() + }; + + workspaceByGlob + .withArgs("/resources/**/themes/*/library.source.less").resolves([ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib1/themes/theme2/library.source.less"], + lessResources["sap/ui/lib1/themes/theme3/library.source.less"], + lessResources["sap/ui/lib2/themes/theme1/library.source.less"], + lessResources["sap/ui/lib2/themes/theme2/library.source.less"], + lessResources["sap/ui/lib2/themes/theme3/library.source.less"], + lessResources["sap/ui/lib3/themes/theme1/library.source.less"], + lessResources["sap/ui/lib3/themes/theme2/library.source.less"], + lessResources["sap/ui/lib3/themes/theme3/library.source.less"] + ]); + + t.context.comboByGlob + .withArgs("/resources/**/(*.library|library.js)").resolves([ + dotLibraryResources["sap/ui/lib1/.library"], + dotLibraryResources["sap/ui/lib1/library.js"], + dotLibraryResources["sap/ui/lib3/library.js"] + ]) + .withArgs("/resources/sap/ui/core/themes/*", {nodir: false}).resolves([ + baseThemes["sap/ui/core/themes/theme1/"], + baseThemes["sap/ui/core/themes/theme3/"] + ]); + + t.context.themeBuilderStub.returns([{}]); + + await buildThemes({ + workspace, + options: { + projectName: "sap.ui.test.lib1", + inputPattern: "/resources/**/themes/*/library.source.less", + librariesPattern: "/resources/**/(*.library|library.js)", + themesPattern: "/resources/sap/ui/core/themes/*" + } + }); + + t.is(t.context.themeBuilderStub.callCount, 1, + "Processor should be called once"); + + t.deepEqual(t.context.themeBuilderStub.getCall(0).args[0], { + resources: [ + lessResources["sap/ui/lib1/themes/theme1/library.source.less"], + lessResources["sap/ui/lib1/themes/theme3/library.source.less"], + lessResources["sap/ui/lib3/themes/theme1/library.source.less"], + lessResources["sap/ui/lib3/themes/theme3/library.source.less"] + ], + fs: {}, + options: { + compress: true, + cssVariables: false + } + }, "Processor should be called with expected arguments"); + + t.is(workspace.write.callCount, 1, + "workspace.write should be called once"); +}); + +test.serial("buildThemes (useWorkers = true)", async (t) => { + t.plan(4); + + const taskUtilMock = { + registerCleanupTask: sinon.stub() + }; + const lessResource = { + getPath: () => "/resources/test/library.source.less", + getBuffer: () => Buffer.from("/** test comment */") + }; + + const workspace = { + byGlob: async (globPattern) => { + if (globPattern === "/resources/test/library.source.less") { + return [lessResource]; + } else { + return []; + } + }, + write: sinon.stub() + }; + + const cssResource = {path: "/cssResource", buffer: new Uint8Array(2)}; + const cssRtlResource = {path: "/cssRtlResource", buffer: new Uint8Array(2)}; + const jsonParametersResource = {path: "/jsonParametersResource", buffer: new Uint8Array(2)}; + + t.context.comboByGlob.resolves([lessResource]); + + t.context.fsInterfaceStub.returns({ + readFile: (...args) => { + if (args[0] === "/resources/test/library.source.less") { + args[args.length - 1](null, "/** */"); + } else { + args[args.length - 1](null, "{}"); + } + }, + stat: (...args) => args[args.length - 1](null, {}), + readdir: (...args) => args[args.length - 1](null, {}), + mkdir: (...args) => args[args.length - 1](null, {}), + }); + + t.context.themeBuilderStub.returns([ + cssResource, + cssRtlResource, + jsonParametersResource + ]); + + await buildThemes({ + workspace, + taskUtil: taskUtilMock, + options: { + projectName: "sap.ui.demo.app", + inputPattern: "/resources/test/library.source.less" + } + }); + + const transferredResources = deserializeResources([cssResource, cssRtlResource, jsonParametersResource]); + + t.is(workspace.write.callCount, 3, + "workspace.write should be called 3 times"); + t.true(workspace.write.calledWithExactly(transferredResources[0])); + t.true(workspace.write.calledWithExactly(transferredResources[1])); + t.true(workspace.write.calledWithExactly(transferredResources[2])); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtilMock.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); + +test.serial("buildThemes with taskUtil and unexpected termination of the workerpool", async (t) => { + const taskUtilMock = { + registerCleanupTask: sinon.stub().callsFake((cb) => { + // Terminate the workerpool in a timeout, so that + // the task is already in the queue, but not yet finished. + setTimeout(cb); + }) + }; + const lessResources = []; + + // Create more resources so there to be some pending tasks in the pool + for (let i = 0; i < 50; i++) { + lessResources.push({ + getPath: () => `/resources/test${i}/themes/${i}/library.source.less`, + getBuffer: () => Buffer.from(`/** test comment N ${i} */`), + }); + } + + const workspace = { + byGlob: async (globPattern) => { + if (globPattern === "/resources/test*/themes/**/library.source.less") { + return lessResources; + } else { + return []; + } + }, + write: sinon.stub() + }; + + const cssResource = {path: "/cssResource", buffer: new Uint8Array(2)}; + const cssRtlResource = {path: "/cssRtlResource", buffer: new Uint8Array(2)}; + const jsonParametersResource = {path: "/jsonParametersResource", buffer: new Uint8Array(2)}; + + t.context.themeBuilderStub.returns([cssResource, cssRtlResource, jsonParametersResource]); + t.context.comboByGlob.resolves(lessResources); + + t.context.fsInterfaceStub.returns({ + readFile: (...args) => { + if (/\/resources\/test.*\/themes\/.*\/library\.source\.less/i.test(args[0])) { + args[args.length - 1](null, "/** */"); + } else { + args[args.length - 1](null, "{}"); + } + }, + stat: (...args) => args[args.length - 1](null, {}), + readdir: (...args) => args[args.length - 1](null, {}), + mkdir: (...args) => args[args.length - 1](null, {}), + }); + + await buildThemes({ + workspace, + taskUtil: taskUtilMock, + options: { + projectName: "sap.ui.demo.app", + inputPattern: "/resources/test*/themes/**/library.source.less" + } + }); + + t.pass("No exception from an earlier workerpool termination attempt."); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtilMock.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/generateBundle.js b/packages/builder/test/lib/tasks/bundlers/generateBundle.js new file mode 100644 index 00000000000..879e50f1062 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateBundle.js @@ -0,0 +1,837 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.log = { + warn: sinon.stub(), + verbose: sinon.stub(), + error: sinon.stub() + }; + t.context.workspace = { + byGlob: sinon.stub().resolves([]), + write: sinon.stub().resolves() + }; + t.context.dependencies = {}; + t.context.combo = { + byGlob: sinon.stub().resolves([]), + filter: sinon.stub() + }; + + t.context.createFilterReaderStub = sinon.stub().returns(t.context.combo); + + const project = { + getVersion: () => "1.120.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(false) + }; + } + }; + + t.context.taskUtil = { + getTag: sinon.stub(), + setTag: sinon.stub(), + clearTag: sinon.stub(), + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: t.context.createFilterReaderStub + }, + getProject: () => project + }; + + t.context.ReaderCollectionPrioritizedStub = sinon.stub().returns(t.context.combo); + t.context.moduleBundlerStub = sinon.stub().resolves([]); + + const ModuleName = await esmock("../../../../lib/lbt/utils/ModuleName.js"); + t.context.getNonDebugName = sinon.stub().callsFake((...args) => { + return ModuleName.getNonDebugName(...args); + }); + + t.context.generateBundle = await esmock("../../../../lib/tasks/bundlers/generateBundle.js", { + "@ui5/fs/ReaderCollectionPrioritized": t.context.ReaderCollectionPrioritizedStub, + "../../../../lib/processors/bundlers/moduleBundler": t.context.moduleBundlerStub + }, { + "../../../../lib/lbt/utils/ModuleName.js": { + getNonDebugName: t.context.getNonDebugName + } + }); +}); + +test.afterEach.always(() => { + sinon.restore(); +}); + +test.serial("generateBundle: No taskUtil, no bundleOptions", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + combo.byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + + await generateBundle({ + workspace, + dependencies, + options: { + projectName: "Test Application", + bundleDefinition + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition, + bundleOptions: undefined + }, + resources + }]); + + t.is(combo.byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(combo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + t.is(createFilterReaderStub.callCount, 0, + "createFilterReaderStub should not have been called"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + +test.serial("generateBundle: No bundleOptions, with taskUtil", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition, + bundleOptions: undefined, + targetUi5CoreVersion: "1.120.0", + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.clearTag.callCount, 1); + t.deepEqual(taskUtil.clearTag.getCall(0).args, + [{"fake": "sourceMap"}, taskUtil.STANDARD_TAGS.OmitFromBuildResult], + "OmitFromBuildResult tag should be cleared on source map resource"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + t.is(taskUtil.getTag.callCount, 0, "taskUtil.getTag should not have been called by the task"); + + // Testing the createFilterReader function + + const resourceForFilterTest = {}; + taskUtil.getTag.returns(true); + t.false(filterFunction(resourceForFilterTest), + "Filter function should return false if the tag is set"); + taskUtil.getTag.returns(false); + t.true(filterFunction(resourceForFilterTest), + "Filter function should return true if the tag is not set"); + + t.is(taskUtil.getTag.callCount, 2); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); +}); + +test.serial("generateBundle: No bundleOptions, with taskUtil and specVersion < 4", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + + const legacyProject = { + getVersion: () => "1.120.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(true) + }; + } + }; + taskUtil.getProject = () => legacyProject; + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: true, + bundleDefinition, + bundleOptions: undefined, + targetUi5CoreVersion: "1.120.0", + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.clearTag.callCount, 1); + t.deepEqual(taskUtil.clearTag.getCall(0).args, + [{"fake": "sourceMap"}, taskUtil.STANDARD_TAGS.OmitFromBuildResult], + "OmitFromBuildResult tag should be cleared on source map resource"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + t.is(taskUtil.getTag.callCount, 0, "taskUtil.getTag should not have been called by the task"); + + // Testing the createFilterReader function + + const resourceForFilterTest = {}; + taskUtil.getTag.returns(true); + t.false(filterFunction(resourceForFilterTest), + "Filter function should return false if the tag is set"); + taskUtil.getTag.returns(false); + t.true(filterFunction(resourceForFilterTest), + "Filter function should return true if the tag is not set"); + + t.is(taskUtil.getTag.callCount, 2); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); +}); + +test.serial("generateBundle: bundleOptions: optimize=false, with taskUtil", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + const resources = [ + { + getPath: sinon.stub().returns("/resources/my/app/module-dbg.js") + }, + { + getPath: sinon.stub().returns("/resources/my/app/Main.view.xml") + } + ]; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + taskUtil.getTag.returns(false) + .withArgs(resources[0], taskUtil.STANDARD_TAGS.IsDebugVariant) + .returns(true); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + const bundleOptions = {optimize: false}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition, + bundleOptions + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition, + bundleOptions, + moduleNameMapping: { + "/resources/my/app/module-dbg.js": "my/app/module.js" + }, + targetUi5CoreVersion: "1.120.0", + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.getTag.callCount, 1, "taskUtil#getTag has been called once"); + t.deepEqual(taskUtil.getTag.getCall(0).args, + [resources[0], taskUtil.STANDARD_TAGS.IsDebugVariant], + "First resource should be checked whether it is a debug variant"); + + t.is(taskUtil.clearTag.callCount, 1); + t.deepEqual(taskUtil.clearTag.getCall(0).args, + [{"fake": "sourceMap"}, taskUtil.STANDARD_TAGS.OmitFromBuildResult], + "OmitFromBuildResult tag should be cleared on source map resource"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + taskUtil.getTag.reset(); // Reset stub as it has already been called by generateBundle + t.is(taskUtil.getTag.callCount, 0); + + // Testing the createFilterReader function + + const resourceForFilterTest = {}; + taskUtil.getTag.returns(true); + t.false(filterFunction(resourceForFilterTest), + "Filter function should return false if the tag is set"); + taskUtil.getTag.returns(false); + t.true(filterFunction(resourceForFilterTest), + "Filter function should return true if the tag is not set"); + + t.is(taskUtil.getTag.callCount, 2); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.HasDebugVariant], + "Resource filtering should be done for resources that have a debug variant, as optimize=false is set"); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.HasDebugVariant], + "Resource filtering should be done for resources that have a debug variant, as optimize=false is set"); +}); + +test.serial("generateBundle: bundleOptions: sourceMap=false, with taskUtil", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + const resources = [ + { + getPath: sinon.stub().returns("/resources/my/app/module-dbg.js") + }, + { + getPath: sinon.stub().returns("/resources/my/app/Main.view.xml") + } + ]; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + taskUtil.getTag.returns(false) + .withArgs(resources[0], taskUtil.STANDARD_TAGS.IsDebugVariant) + .returns(true); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + const bundleOptions = {sourceMap: false}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition, + bundleOptions + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition, + bundleOptions, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.getTag.callCount, 0); + + t.is(taskUtil.clearTag.callCount, 0, + "clearTag should not be called as no source map resource is created"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 1, + "workspace.write should have been called once"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + taskUtil.getTag.reset(); // Reset stub as it has already been called by generateBundle + t.is(taskUtil.getTag.callCount, 0); + + // Testing the createFilterReader function + + const resourceForFilterTest = {}; + taskUtil.getTag.returns(true); + t.false(filterFunction(resourceForFilterTest), + "Filter function should return false if the tag is set"); + taskUtil.getTag.returns(false); + t.true(filterFunction(resourceForFilterTest), + "Filter function should return true if the tag is not set"); + + t.is(taskUtil.getTag.callCount, 2); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); +}); + +test.serial("generateBundle: Empty bundle (skipIfEmpty=true)", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + const resources = []; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + moduleBundlerStub.resolves([undefined]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + const bundleOptions = {skipIfEmpty: true}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition, + bundleOptions + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition, + bundleOptions, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.getTag.callCount, 0); + + t.is(taskUtil.clearTag.callCount, 0, + "clearTag should not be called as no source map resource is created"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + t.is(workspace.write.callCount, 0, + "workspace.write should have been called once"); +}); + +test.serial("generateBundle: Throws error when non-debug name can't be resolved", async (t) => { + const { + generateBundle, moduleBundlerStub, + workspace, dependencies, createFilterReaderStub, + taskUtil, getNonDebugName + } = t.context; + + const resources = [ + { + getPath: sinon.stub().returns("/resources/my/app/module.js") + } + ]; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + moduleBundlerStub.resolves([undefined]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + const bundleOptions = {optimize: false}; + + taskUtil.getTag.returns(true); + getNonDebugName.returns(false); + + await t.throwsAsync(generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition, + bundleOptions + } + }), { + message: "Failed to resolve non-debug name for /resources/my/app/module.js" + }); +}); + +test.serial("generateBundle: No bundleOptions, with taskUtil, UI5 Version >= 2", async (t) => { + const { + generateBundle, moduleBundlerStub, ReaderCollectionPrioritizedStub, + workspace, dependencies, combo, createFilterReaderStub, + taskUtil + } = t.context; + + taskUtil.getProject = () => { + return { + getVersion: () => "2.0.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(false) + }; + } + }; + }; + + const resources = [ + {"fake": "resource"} + ]; + + const filteredCombo = { + byGlob: sinon.stub().resolves(resources) + }; + createFilterReaderStub.returns(filteredCombo); + + moduleBundlerStub.resolves([ + { + name: "my/app/customBundle.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + // bundleDefinition can be empty here as the moduleBundler is mocked + const bundleDefinition = {}; + + await generateBundle({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + bundleDefinition + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition, + bundleOptions: undefined, + targetUi5CoreVersion: "2.0.0", + }, + resources + }]); + + t.is(combo.byGlob.callCount, 0, + "combo.byGlob should not have been called"); + + t.is(createFilterReaderStub.callCount, 1, + "createFilterReader should have been called once"); + t.is(createFilterReaderStub.getCall(0).args.length, 1, + "createFilterReader should have been called with one argument"); + const filterFunction = createFilterReaderStub.getCall(0).args[0].callback; + t.is(typeof filterFunction, "function", + "createFilterReader should have been called with a function"); + const filterReader = createFilterReaderStub.getCall(0).args[0].reader; + t.is(filterReader, combo, + "createFilterReader should have been called with correct reader instance"); + + t.is(filteredCombo.byGlob.callCount, 1, + "filteredCombo.byGlob should have been called once"); + t.deepEqual(filteredCombo.byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "filteredCombo.byGlob should have been called with expected pattern"); + + t.is(taskUtil.clearTag.callCount, 1); + t.deepEqual(taskUtil.clearTag.getCall(0).args, + [{"fake": "sourceMap"}, taskUtil.STANDARD_TAGS.OmitFromBuildResult], + "OmitFromBuildResult tag should be cleared on source map resource"); + + t.is(ReaderCollectionPrioritizedStub.callCount, 1, + "ReaderCollectionPrioritized should have been called once"); + t.true(ReaderCollectionPrioritizedStub.calledWithNew(), + "ReaderCollectionPrioritized should have been called with 'new'"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + t.is(taskUtil.getTag.callCount, 0, "taskUtil.getTag should not have been called by the task"); + + // Testing the createFilterReader function + + const resourceForFilterTest = {}; + taskUtil.getTag.returns(true); + t.false(filterFunction(resourceForFilterTest), + "Filter function should return false if the tag is set"); + taskUtil.getTag.returns(false); + t.true(filterFunction(resourceForFilterTest), + "Filter function should return true if the tag is not set"); + + t.is(taskUtil.getTag.callCount, 2); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resourceForFilterTest, taskUtil.STANDARD_TAGS.IsDebugVariant], + "Resource filtering should be done for debug variants as optimize=true is the default"); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/generateComponentPreload.js b/packages/builder/test/lib/tasks/bundlers/generateComponentPreload.js new file mode 100644 index 00000000000..6231d9adfe3 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateComponentPreload.js @@ -0,0 +1,977 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.log = { + warn: sinon.stub(), + verbose: sinon.stub(), + error: sinon.stub() + }; + + t.context.workspace = { + byGlob: sinon.stub().resolves([]), + write: sinon.stub().resolves() + }; + + t.context.dependencies = {}; + t.context.byGlob = t.context.workspace.byGlob; + + t.context.moduleBundlerStub = sinon.stub().resolves([]); + + t.context.generateComponentPreload = await esmock("../../../../lib/tasks/bundlers/generateComponentPreload.js", { + "../../../../lib/processors/bundlers/moduleBundler": t.context.moduleBundlerStub, + "@ui5/logger": { + getLogger: sinon.stub().withArgs("builder:tasks:bundlers:generateComponentPreload").returns(t.context.log) + } + }); +}); + +test.afterEach.always(() => { + sinon.restore(); +}); + +test.serial("generateComponentPreload - one namespace", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/Component-preload.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + await generateComponentPreload({ + workspace, + dependencies, + options: { + projectName: "Test Application", + namespaces: ["my/app"] + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app/Component-preload.js", + sections: [ + { + filters: [ + "my/app/", + "my/app/**/manifest.json", + "my/app/changes/changes-bundle.json", + "my/app/changes/flexibility-bundle.json", + "!my/app/test/", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + +test.serial("generateComponentPreload - one namespace, with taskUtil and specVersion < 4", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/Component-preload.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + const project = { + getVersion: () => "1.120.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(true) + }; + } + }; + + const taskUtil = { + getProject: () => project, + getTag: sinon.stub().returns(false), + setTag: sinon.stub().returns(), + clearTag: sinon.stub().returns(), + STANDARD_TAGS: { + IsBundle: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + + await generateComponentPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + namespaces: ["my/app"] + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app/Component-preload.js", + sections: [ + { + filters: [ + "my/app/", + "my/app/**/manifest.json", + "my/app/changes/changes-bundle.json", + "my/app/changes/flexibility-bundle.json", + "!my/app/test/", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + +test.serial("generateComponentPreload - one namespace, with taskUtil and specVersion >= 4", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/Component-preload.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + const project = { + getVersion: () => "1.120.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(false) + }; + } + }; + + const taskUtil = { + getProject: () => project, + getTag: sinon.stub().returns(false), + setTag: sinon.stub().returns(), + clearTag: sinon.stub().returns(), + STANDARD_TAGS: { + IsBundle: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + + await generateComponentPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + namespaces: ["my/app"] + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app/Component-preload.js", + sections: [ + { + filters: [ + "my/app/", + "my/app/**/manifest.json", + "my/app/changes/changes-bundle.json", + "my/app/changes/flexibility-bundle.json", + "!my/app/test/", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + + +test.serial("generateComponentPreload - one namespace - excludes", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/Component-preload.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + await generateComponentPreload({ + workspace, + dependencies, + options: { + projectName: "Test Application", + namespaces: ["my/app"], + excludes: [ + "my/app/thirdparty/", + "!my/app/thirdparty/NotExcluded.js" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app/Component-preload.js", + sections: [ + { + filters: [ + "my/app/", + "my/app/**/manifest.json", + "my/app/changes/changes-bundle.json", + "my/app/changes/flexibility-bundle.json", + "!my/app/test/", + "!my/app/thirdparty/", + "+my/app/thirdparty/NotExcluded.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + +test.serial("generateComponentPreload - one namespace - excludes w/o namespace", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.resolves([ + { + name: "my/app/Component-preload.js", + bundle: {"fake": "bundle"}, + sourceMap: {"fake": "sourceMap"} + } + ]); + + const project = { + getVersion: () => "1.120.0", + getSpecVersion() { + return { + lt: sinon.stub().withArgs("4.0").returns(false) + }; + } + }; + + const taskUtil = { + getProject: () => project, + getTag: sinon.stub().returns(false), + setTag: sinon.stub().returns(), + clearTag: sinon.stub().returns(), + STANDARD_TAGS: { + IsBundle: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + await generateComponentPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "Test Application", + namespaces: ["my/app"], + excludes: [ + "thirdparty/", + "!thirdparty/NotExcluded.js" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app/Component-preload.js", + sections: [ + { + filters: [ + "my/app/", + "my/app/**/manifest.json", + "my/app/changes/changes-bundle.json", + "my/app/changes/flexibility-bundle.json", + "!my/app/test/", + "!thirdparty/", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleResources = await moduleBundlerStub.getCall(0).returnValue; + t.is(workspace.write.callCount, 2, + "workspace.write should have been called twice"); + t.deepEqual(workspace.write.getCall(0).args, [bundleResources[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleResources[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleResources[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleResources[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + t.is(taskUtil.getTag.callCount, 0, "TaskUtil#getTag never got called"); + + t.is(taskUtil.setTag.callCount, 1, "TaskUtil#setTag got called once"); + t.deepEqual(taskUtil.setTag.getCall(0).args[0], {"fake": "bundle"}, + "TaskUtil#setTag got called with expected resource"); + t.is(taskUtil.setTag.getCall(0).args[1], "", + "TaskUtil#setTag got called with expected tag"); + t.is(taskUtil.setTag.getCall(0).args[2], undefined, // defaults to true internally + "TaskUtil#setTag got called with expected tag value"); + + t.is(taskUtil.clearTag.callCount, 1, "TaskUtil#clearTag got called once"); + t.deepEqual(taskUtil.clearTag.getCall(0).args[0], {"fake": "sourceMap"}, + "TaskUtil#clearTag got called with expected resource"); + t.is(taskUtil.clearTag.getCall(0).args[1], "", + "TaskUtil#clearTag got called with expected tag"); +}); + +test.serial("generateComponentPreload - multiple namespaces - excludes", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + moduleBundlerStub.onFirstCall().resolves([ + { + name: "my/app1/Component-preload.js", + bundle: {"fake": "bundle1"}, + sourceMap: {"fake": "sourceMap1"} + } + ]); + moduleBundlerStub.onSecondCall().resolves([ + { + name: "my/app2/Component-preload.js", + bundle: {"fake": "bundle2"}, + sourceMap: {"fake": "sourceMap2"} + } + ]); + + await generateComponentPreload({ + workspace, + dependencies, + options: { + projectName: "Test Application", + namespaces: [ + "my/app1", + "my/app2" + ], + excludes: [ + "my/app1/thirdparty1/", + "!my/app1/thirdparty1/NotExcluded.js", + "my/app2/thirdparty2/", + "!my/app2/thirdparty2/NotExcluded.js" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 2, "moduleBundler should have been called twice"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app1/Component-preload.js", + sections: [ + { + filters: [ + "my/app1/", + "my/app1/**/manifest.json", + "my/app1/changes/changes-bundle.json", + "my/app1/changes/flexibility-bundle.json", + "!my/app1/test/", + "!my/app1/thirdparty1/", + "+my/app1/thirdparty1/NotExcluded.js", + "!my/app2/thirdparty2/", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/app2/Component-preload.js", + sections: [ + { + filters: [ + "my/app2/", + "my/app2/**/manifest.json", + "my/app2/changes/changes-bundle.json", + "my/app2/changes/flexibility-bundle.json", + "!my/app2/test/", + "!my/app1/thirdparty1/", + "!my/app2/thirdparty2/", + "+my/app2/thirdparty2/NotExcluded.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(byGlob.callCount, 1, + "combo.byGlob should have been called once"); + t.deepEqual(byGlob.getCall(0).args, ["/resources/**/*.{js,json,xml,html,properties,library,js.map}"], + "combo.byGlob should have been called with expected pattern"); + + const bundleObj1 = await moduleBundlerStub.getCall(0).returnValue; + const bundleObj2 = await moduleBundlerStub.getCall(1).returnValue; + + t.is(workspace.write.callCount, 4, + "workspace.write should have been called 4 times (2x .js, 2x .js.map)"); + + t.deepEqual(workspace.write.getCall(0).args, [bundleObj1[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(0).args[0], bundleObj1[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(1).args, [bundleObj1[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(1).args[0], bundleObj1[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); + + t.deepEqual(workspace.write.getCall(2).args, [bundleObj2[0].bundle], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(2).args[0], bundleObj2[0].bundle, + "workspace.write should have been called with exact resource returned by moduleBundler"); + t.deepEqual(workspace.write.getCall(3).args, [bundleObj2[0].sourceMap], + "workspace.write should have been called with expected args"); + t.is(workspace.write.getCall(3).args[0], bundleObj2[0].sourceMap, + "workspace.write should have been called with exact resource returned by moduleBundler"); +}); + +test.serial("generateComponentPreload - one namespace - invalid exclude", async (t) => { + const { + generateComponentPreload, + workspace, dependencies, byGlob, + log + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + await generateComponentPreload({ + workspace, + dependencies, + options: { + projectName: "Test Application", + namespaces: ["my/app"], + excludes: [ + "!**/" // re-include outside of namespace is not allowed + ] + } + }); + + t.is(log.warn.callCount, 1, "log.warn should be called once"); + t.deepEqual(log.warn.getCall(0).args, [ + "Configured preload exclude contains invalid re-include: !**/. " + + "Re-includes must start with a component namespace (my/app)" + ]); + + t.is(log.verbose.callCount, 1, "log.verbose should be called once"); + t.deepEqual(log.verbose.getCall(0).args, [ + "Generating my/app/Component-preload.js..." + ]); + + t.is(log.error.callCount, 0, "log.error should not be called"); +}); + +test.serial("generateComponentPreload - nested namespaces - excludes", async (t) => { + const { + generateComponentPreload, moduleBundlerStub, + workspace, dependencies, byGlob, + log + } = t.context; + + const resources = [ + {"fake": "resource"} + ]; + byGlob.resolves(resources); + + await generateComponentPreload({ + workspace, + dependencies, + options: { + projectName: "Test Application", + namespaces: [ + "my/project/component1", + "my/project", + "my/project/component2", + ], + excludes: [ + "my/project/component1/foo/", + "!my/project/test/", + "!my/project/component2/*.html", + + // Invalid, should cause a warning + "!invalid/namespace/" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/project/component1/Component-preload.js", + sections: [ + { + filters: [ + "my/project/component1/", + "my/project/component1/**/manifest.json", + "my/project/component1/changes/changes-bundle.json", + "my/project/component1/changes/flexibility-bundle.json", + "!my/project/component1/test/", + + // via excludes config + "!my/project/component1/foo/" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/project/Component-preload.js", + sections: [ + { + filters: [ + "my/project/", + "my/project/**/manifest.json", + "my/project/changes/changes-bundle.json", + "my/project/changes/flexibility-bundle.json", + "!my/project/test/", + + // via excludes config + "!my/project/component1/foo/", + "+my/project/test/", + "+my/project/component2/*.html", + + // sub-namespaces are excluded + "!my/project/component1/", + "!my/project/component1/**/manifest.json", + "!my/project/component2/", + "!my/project/component2/**/manifest.json", + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], + name: "my/project/component2/Component-preload.js", + sections: [ + { + filters: [ + "my/project/component2/", + "my/project/component2/**/manifest.json", + "my/project/component2/changes/changes-bundle.json", + "my/project/component2/changes/flexibility-bundle.json", + "!my/project/component2/test/", + + // via excludes config + "!my/project/component1/foo/", + "+my/project/component2/*.html" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(log.warn.callCount, 1, "log.warn should be called once"); + t.deepEqual(log.warn.getCall(0).args, [ + "Configured preload exclude contains invalid re-include: !invalid/namespace/. " + + "Re-includes must start with a component namespace " + + "(my/project/component1 or my/project or my/project/component2)" + ]); + + t.is(log.verbose.callCount, 3, "log.verbose should be called once"); + t.deepEqual(log.verbose.getCall(0).args, [ + "Generating my/project/component1/Component-preload.js..." + ]); + t.deepEqual(log.verbose.getCall(1).args, [ + "Generating my/project/Component-preload.js..." + ]); + t.deepEqual(log.verbose.getCall(2).args, [ + "Generating my/project/component2/Component-preload.js..." + ]); + t.is(log.error.callCount, 0, "log.error should not be called"); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/generateFlexChangesBundle.js b/packages/builder/test/lib/tasks/bundlers/generateFlexChangesBundle.js new file mode 100644 index 00000000000..700adb37263 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateFlexChangesBundle.js @@ -0,0 +1,234 @@ +import test from "ava"; +import sinon from "sinon"; +import generateFlexChangesBundle from "../../../../lib/tasks/bundlers/generateFlexChangesBundle.js"; + + +function createPlaceholderResource(content) { + return { + name: "file", + getBuffer: async () => JSON.stringify(content), + getString: () => JSON.stringify(content), + setString: (string) => undefined + }; +} + +function createPlaceholderWorkspace(changes, manifest, flexBundle) { + return { + byGlob: async (path) => changes.map(createPlaceholderResource), + byPath: async (path) => { + if ( path.includes("manifest.json") ) { + return createPlaceholderResource(manifest); + } else if ( path.includes("flexibility-bundle.json")) { + return createPlaceholderResource(flexBundle); + } + }, + write: () => { + throw new Error("Function 'write' is not implemented"); + } + }; +} + +["1.120.0", ["1.120.0", "2.0.0"]].forEach((minVersion) => { + test.serial(`execute flexChangeBundler with the minVersion: ${minVersion}`, async (t) => { + const changeList = [ + { + "fileName": "id_1504764957625_7_rename1", + "fileType": "change", + "changeType": "rename", + "reference": "rta.performance.Component", + "packageName": "$TMP", + "content": { + "originalControlType": "sap.m.Label" + }, + "selector": { + "id": "initialLabel", + "idIsLocal": false + }, + "layer": "CUSTOMER", + "texts": { + "newText": { + "value": "rename_0", + "type": "XFLD" + } + }, + "namespace": "apps/MyComponent/changes/", + "creation": "2017-10-06T11:54:55.238Z", + "originalLanguage": "EN", + "conditions": {}, + "context": "", + "support": { + "generator": "Change.createInitialFileContent", + "service": "", + "user": "", + "sapui5Version": "1.51.0-SNAPSHOT" + }, + "dependentSelector": {}, + "validAppVersions": { + "creation": "1.0.0", + "from": "1.0.0", + "to": "1.0.0" + } + } + ]; + const existingChangeList = [ + { + "fileName": "id_1504764957630_7_rename2", + "fileType": "change", + "changeType": "rename", + "reference": "rta.performance.Component", + "packageName": "$TMP", + "content": { + "originalControlType": "sap.m.Label" + }, + "selector": { + "id": "initialLabel", + "idIsLocal": false + }, + "layer": "USER", + "texts": { + "newText": { + "value": "rename_5", + "type": "XFLD" + } + }, + "namespace": "apps/MyComponent/changes/", + "creation": "2017-09-01T11:54:55.238Z", + "originalLanguage": "EN", + "conditions": {}, + "context": "", + "support": { + "generator": "Change.createInitialFileContent", + "service": "", + "user": "", + "sapui5Version": "1.51.0-SNAPSHOT" + }, + "dependentSelector": {}, + "validAppVersions": { + "creation": "1.0.0", + "from": "1.0.0", + "to": "1.0.0" + } + } + ]; + const manifest = { + "sap.ui5": { + dependencies: { + minUI5Version: minVersion + } + } + }; + + const flexBundle = { + "changes": existingChangeList, + "compVariants": [], + "variantChanges": [], + "variantDependentControlChanges": [], + "variantManagementChanges": [], + "variants": [] + }; + + const flexBundleMerge = { + "changes": existingChangeList.concat(changeList), + "compVariants": [], + "variantChanges": [], + "variantDependentControlChanges": [], + "variantManagementChanges": [], + "variants": [] + }; + + const placeholderWorkspace = createPlaceholderWorkspace(changeList, manifest, flexBundle); + const stub = sinon.stub(placeholderWorkspace, "write").returnsArg(0); + await generateFlexChangesBundle({ + workspace: placeholderWorkspace, + taskUtil: false, + options: { + projectNamespace: "mypath" + } + }); + + const content = JSON.parse(await stub.getCall(0).args[0].getString()); + t.deepEqual(content, flexBundleMerge, "Result must contain the same content"); + + const path = await stub.getCall(0).args[0].getPath(); + t.is(path, "/resources/mypath/changes/flexibility-bundle.json"); + }); +}); + +["1.70.0", ["1.70.0", "2.0.0"]].forEach((minVersion) => { + test.serial(`execute flexChangeBundler with the minVersion < 1.73: ${minVersion}`, async (t) => { + const manifest = { + "sap.ui5": { + dependencies: { + minUI5Version: minVersion + } + } + }; + + const changeList = [ + { + "fileName": "id_1504764957625_7_rename1", + "fileType": "change", + "changeType": "rename", + "reference": "rta.performance.Component", + "packageName": "$TMP", + "content": { + "originalControlType": "sap.m.Label" + }, + "selector": { + "id": "initialLabel", + "idIsLocal": false + }, + "layer": "CUSTOMER", + "texts": { + "newText": { + "value": "rename_0", + "type": "XFLD" + } + }, + "namespace": "apps/MyComponent/changes/", + "creation": "2017-10-06T11:54:55.238Z", + "originalLanguage": "EN", + "conditions": {}, + "context": "", + "support": { + "generator": "Change.createInitialFileContent", + "service": "", + "user": "", + "sapui5Version": "1.51.0-SNAPSHOT" + }, + "dependentSelector": {}, + "validAppVersions": { + "creation": "1.0.0", + "from": "1.0.0", + "to": "1.0.0" + } + } + ]; + + const flexBundle = { + "changes": [], + "compVariants": [], + "variantChanges": [], + "variantDependentControlChanges": [], + "variantManagementChanges": [], + "variants": [] + }; + + const placeholderWorkspace = createPlaceholderWorkspace(changeList, manifest, flexBundle); + const stub = sinon.stub(placeholderWorkspace, "write").returnsArg(0); + + await generateFlexChangesBundle({ + workspace: placeholderWorkspace, + taskUtil: false, + options: { + projectNamespace: "mypath" + } + }); + + const content = JSON.parse(await stub.getCall(0).args[0].getString()); + t.deepEqual(content, changeList, "Result must contain the same content"); + + const path = await stub.getCall(0).args[0].getPath(); + t.is(path, "/resources/mypath/changes/changes-bundle.json"); + }); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js new file mode 100644 index 00000000000..173e5f93fbc --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js @@ -0,0 +1,413 @@ +import test from "ava"; +import path from "node:path"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; +import {graphFromObject} from "@ui5/project/graph"; +import {directoryDeepEqual, fileEqual, findFiles} from "../../../utils/fshelper.js"; +import generateLibraryPreload from "../../../../lib/tasks/bundlers/generateLibraryPreload.js"; +import * as taskRepository from "../../../../lib/tasks/taskRepository.js"; + +const __dirname = import.meta.dirname; +const libraryDPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.d"); +const libraryDMinifiedPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.d-minified"); +const sapUiCorePath = path.join(__dirname, "..", "..", "..", "fixtures", "sap.ui.core"); +const libraryNPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.n"); + +test.serial("integration: build library.d with library preload", async (t) => { + const destPath = "./test/tmp/build/library.d/preload"; + const expectedPath = "./test/expected/build/library.d/preload"; + const excludedTasks = ["*"]; + const includedTasks = ["generateLibraryPreload"]; + + const graph = await graphFromObject({ + dependencyTree: libraryDTree + }); + graph.setTaskRepository(taskRepository); + await t.notThrowsAsync(graph.build({ + destPath, + excludedTasks, + includedTasks + })); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + t.is(expectedFiles.length, 8, "8 files are expected"); + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const libraryDTree = { + "id": "library.d", + "version": "1.0.0", + "path": libraryDPath, + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.d", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + } + } + } + } +}; + +test.serial("integration: build library.d-minified with library preload", async (t) => { + const destPath = "./test/tmp/build/library.d-minified/preload"; + const expectedPath = "./test/expected/build/library.d-minified/preload"; + const excludedTasks = ["*"]; + const includedTasks = ["generateLibraryPreload"]; + + const graph = await graphFromObject({ + dependencyTree: libraryDMinifiedTree + }); + graph.setTaskRepository(taskRepository); + await t.notThrowsAsync(graph.build({ + destPath, + excludedTasks, + includedTasks + })); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + t.is(expectedFiles.length, 11, "11 files are expected"); + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const libraryDMinifiedTree = { + "id": "library.d", + "version": "1.0.0", + "path": libraryDMinifiedPath, + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.d", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + } + } + } + } +}; + +test.serial("integration: build sap.ui.core with library preload", async (t) => { + const destPath = "./test/tmp/build/sap.ui.core/preload"; + const expectedPath = "./test/expected/build/sap.ui.core/preload"; + const excludedTasks = ["*"]; + const includedTasks = ["minify", "generateLibraryPreload"]; + + const graph = await graphFromObject({ + dependencyTree: sapUiCoreTree + }); + graph.setTaskRepository(taskRepository); + await t.notThrowsAsync(graph.build({ + destPath, + excludedTasks, + includedTasks + })); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const sapUiCoreTree = { + "id": "sap.ui.core", + "version": "1.0.0", + "path": sapUiCorePath, + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + } + } + } + } +}; + + +test.serial("integration: generateLibraryPreload", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + await reader.write(createResource({ + path: "/resources/my/test/lib/library.js", + string: "" + })); + + const writer = createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = createAdapter({ + virBasePath: "/" + }); + + await generateLibraryPreload({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + projectName: "my.test.lib" + } + }); + + const writtenResources = await writer.byGlob(["**/**"]); + t.deepEqual(writtenResources.map((r) => r.getPath()).sort(), [ + "/resources/my/test/lib/library-preload.js", + "/resources/my/test/lib/library-preload.js.map" + ], "Expected preload files should be created"); + + const libraryPreload = await writer.byPath("/resources/my/test/lib/library-preload.js"); + t.truthy(libraryPreload, "library-preload.js should have been created"); + const libraryPreloadContent = await libraryPreload.getString(); + t.true(libraryPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.js"), + "library-preload should be a bundle"); + t.regex(libraryPreloadContent, new RegExp("my/test/lib/library"), + "library-preload should include library.js module"); + + const libraryPreloadSourceMap = await writer.byPath("/resources/my/test/lib/library-preload.js.map"); + const libraryPreloadSourceMapContent = await libraryPreloadSourceMap.getString(); + t.notThrows(() => { + JSON.parse(libraryPreloadSourceMapContent); + }, "Source map file should have valid JSON content"); +}); + +test.serial("integration: generateLibraryPreload with designtime and support files", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + await reader.write(createResource({ + path: "/resources/my/test/lib/library.js", + string: "" + })); + + // designtime + await reader.write(createResource({ + path: "/resources/my/test/lib/designtime/foo.js", + string: "" + })); + await reader.write(createResource({ + path: "/resources/my/test/lib/some.designtime.js", + string: "" + })); + + // support + await reader.write(createResource({ + path: "/resources/my/test/lib/some.support.js", + string: "" + })); + await reader.write(createResource({ + path: "/resources/my/test/lib/support/foo.support.js", + string: "" + })); + + const writer = createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = createAdapter({ + virBasePath: "/" + }); + + await generateLibraryPreload({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + projectName: "my.test.lib" + } + }); + + const writtenResources = await writer.byGlob(["**/**"]); + t.deepEqual(writtenResources.map((r) => r.getPath()).sort(), [ + "/resources/my/test/lib/designtime/library-preload.designtime.js", + "/resources/my/test/lib/designtime/library-preload.designtime.js.map", + "/resources/my/test/lib/library-preload.js", + "/resources/my/test/lib/library-preload.js.map", + "/resources/my/test/lib/library-preload.support.js", + "/resources/my/test/lib/library-preload.support.js.map", + ], "Expected preload files should be created"); + + const libraryPreload = await writer.byPath("/resources/my/test/lib/library-preload.js"); + t.truthy(libraryPreload, "library-preload.js should have been created"); + const libraryPreloadContent = await libraryPreload.getString(); + t.true(libraryPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.js"), + "library-preload should be a bundle"); + t.regex(libraryPreloadContent, new RegExp("my/test/lib/library"), + "library-preload should include library.js module"); + + const designtimePreload = await writer.byPath("/resources/my/test/lib/designtime/library-preload.designtime.js"); + t.truthy(designtimePreload, "library-preload.js should have been created"); + const designtimePreloadContent = await designtimePreload.getString(); + t.true(designtimePreloadContent.includes("//@ui5-bundle my/test/lib/designtime/library-preload.designtime.js"), + "library-preload.designtime should be a bundle"); + t.regex(designtimePreloadContent, new RegExp("my/test/lib/designtime/foo"), + "library-preload should include designtime/foo module"); + t.regex(designtimePreloadContent, new RegExp("my/test/lib/some\\.designtime"), + "library-preload should include some.designtime module"); + + const supportPreload = await writer.byPath("/resources/my/test/lib/library-preload.support.js"); + t.truthy(supportPreload, "library-preload.js should have been created"); + const supportPreloadContent = await supportPreload.getString(); + t.true(supportPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.support.js"), + "library-preload.support should be a bundle"); + t.regex(supportPreloadContent, new RegExp("my/test/lib/some\\.support"), + "library-preload.support should include some.support module"); + t.regex(supportPreloadContent, new RegExp("my/test/lib/support/foo\\.support"), + "library-preload.support should include support/foo.support module"); + + const libraryPreloadSourceMap = await writer.byPath("/resources/my/test/lib/library-preload.js.map"); + const libraryPreloadSourceMapContent = await libraryPreloadSourceMap.getString(); + t.notThrows(() => { + JSON.parse(libraryPreloadSourceMapContent); + }, "Source map file should have valid JSON content"); + + const designtimePreloadSourceMap = + await writer.byPath("/resources/my/test/lib/designtime/library-preload.designtime.js.map"); + const designtimePreloadSourceMapContent = await designtimePreloadSourceMap.getString(); + t.notThrows(() => { + JSON.parse(designtimePreloadSourceMapContent); + }, "Source map file should have valid JSON content"); + + const supportPreloadSourceMap = + await writer.byPath("/resources/my/test/lib/library-preload.support.js.map"); + const supportPreloadSourceMapContent = await supportPreloadSourceMap.getString(); + t.notThrows(() => { + JSON.parse(supportPreloadSourceMapContent); + }, "Source map file should have valid JSON content"); +}); + +test.serial("integration: build library.n without enabled string bundling", async (t) => { + const destPath = "./test/tmp/build/library.n/dest"; + const expectedPath = "./test/expected/build/library.n/dest"; + const excludedTasks = ["*"]; + const includedTasks = ["generateLibraryPreload"]; + + const graph = await graphFromObject({ + dependencyTree: libraryNTree + }); + graph.setTaskRepository(taskRepository); + await t.notThrowsAsync(graph.build({ + destPath, + excludedTasks, + includedTasks + })); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + t.is(expectedFiles.length, 6, "6 files are expected"); + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const libraryNTree = { + "id": "library.n", + "version": "1.0.0", + "path": libraryNPath, + "dependencies": [], + "configuration": { + "specVersion": "4.0", + "type": "library", + "metadata": { + "name": "library.n", + "copyright": "Some fancy copyright" + } + } +}; + +test.serial("integration: build library.n with enabled string bundling", async (t) => { + const destPath = "./test/tmp/build/library.n/legacy"; + const expectedPath = "./test/expected/build/library.n/legacy"; + const excludedTasks = ["*"]; + const includedTasks = ["generateLibraryPreload"]; + + const graph = await graphFromObject({ + dependencyTree: libraryNTreeLegacy + }); + graph.setTaskRepository(taskRepository); + await t.notThrowsAsync(graph.build({ + destPath, + excludedTasks, + includedTasks + })); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + t.is(expectedFiles.length, 6, "6 files are expected"); + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const libraryNTreeLegacy = { + "id": "library.n", + "version": "1.0.0", + "path": libraryNPath, + "dependencies": [], + "configuration": { + "specVersion": "3.1", + "type": "library", + "metadata": { + "name": "library.n", + "copyright": "Some fancy copyright" + } + } +}; diff --git a/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.js b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.js new file mode 100644 index 00000000000..a3b579547e4 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.js @@ -0,0 +1,2304 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.log = { + warn: sinon.stub(), + verbose: sinon.stub(), + error: sinon.stub() + }; + + t.context.workspace = { + byGlob: sinon.stub().resolves([]), + write: sinon.stub().resolves() + }; + + t.context.dependencies = {}; + t.context.firstByGlob = t.context.workspace.byGlob.onFirstCall(); + t.context.secondByGlob = t.context.workspace.byGlob.onSecondCall(); + + t.context.moduleBundlerStub = sinon.stub().resolves([]); + + t.context.generateLibraryPreload = await esmock("../../../../lib/tasks/bundlers/generateLibraryPreload.js", { + "../../../../lib/processors/bundlers/moduleBundler.js": t.context.moduleBundlerStub, + "@ui5/logger": { + getLogger: sinon.stub().withArgs("builder:tasks:bundlers:generateLibraryPreload").returns(t.context.log) + } + }); +}); + +test.afterEach.always(() => { + sinon.restore(); +}); + +test.serial("generateLibraryPreload", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]); + + await generateLibraryPreload({ + workspace, + dependencies, + options: { + projectName: "Test Library" + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "my/lib/library-preload.js", + sections: [ + { + filters: [ + "my/lib/", + "my/lib/**/manifest.json", + "!my/lib/**/*-preload.js", + "!my/lib/designtime/", + "!my/lib/**/*.designtime.js", + "!my/lib/**/*.support.js", + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "my/lib/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "my/lib/**/*.designtime.js", + "my/lib/designtime/", + "!my/lib/**/*-preload.designtime.js", + "!my/lib/designtime/**/*.properties", + "!my/lib/designtime/**/*.svg", + "!my/lib/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "my/lib/library-preload.support.js", + sections: [ + { + filters: [ + "my/lib/**/*.support.js", + "!my/lib/**/*-preload.support.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true, + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources + }]); + + t.is(workspace.byGlob.callCount, 2, + "workspace.byGlob should have been called twice"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); +}); + +test.serial("generateLibraryPreload for sap.ui.core (w/o ui5loader.js)", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + await generateLibraryPreload({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + // Should be ignored for hardcoded sap.ui.core bundle configuration + excludes: ["sap/ui/core/**"] + } + }); + + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 7 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "sap-ui-core.js", + sections: [ + { + filters: [ + "jquery.sap.global.js" + ], + mode: "raw", + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + declareModules: false + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "sap-ui-core-dbg.js", + sections: [ + { + filters: [ + "jquery.sap.global.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "sap-ui-core-nojQuery.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "jquery.sap.global.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + resolveConditional: false, + sort: true, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(3).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "sap-ui-core-nojQuery-dbg.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "jquery.sap.global.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + } + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(4).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + resolve: true, + resolveConditional: false, + sort: true, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + renderer: true, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(workspace.byGlob.callCount, 2, + "workspace.byGlob should have been called twice"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); +}); + +test.serial("generateLibraryPreload for sap.ui.core (/w ui5loader.js)", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob, secondByGlob, + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/ui5loader.js")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + secondByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + getSpecVersion: () => { + return { + toString: () => "0.1", + gte: sinon.stub().withArgs("4.0").returns(false), + lte: sinon.stub().withArgs("2.0").returns(true), + lt: sinon.stub().withArgs("4.0").returns(true) + }; + }, + getVersion: () => "1.120.0" + }; + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + await generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core", + // Should be ignored for hardcoded sap.ui.core bundle configuration + excludes: ["sap/ui/core/**"] + } + }); + + t.is(workspace.byGlob.callCount, 3, + "workspace.byGlob should have been called three times"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(2).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); + + t.is(taskUtil.getTag.callCount, 2, "TaskUtil#getTag got called two times"); + t.is(taskUtil.getTag.getCall(0).args[0], resources[2], + "TaskUtil#getTag got called with expected resource on first call"); + t.is(taskUtil.getTag.getCall(0).args[1], "", + "TaskUtil#getTag got called with expected tag on first call"); + t.is(taskUtil.getTag.getCall(1).args[0], resources[1], + "TaskUtil#getTag got called with expected resource on second call"); + t.is(taskUtil.getTag.getCall(1).args[1], "", + "TaskUtil#getTag got called with expected tag on second call"); + + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 7 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + async: false + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-dbg.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + declareRawModules: false, + resolve: true, + sort: true, + declareModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-nojQuery.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + declareRawModules: false, + renderer: false, + resolveConditional: false, + sort: true, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + renderer: false, + declareRawModules: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(3).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-nojQuery-dbg.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + declareModules: false, + declareRawModules: false, + renderer: false, + resolve: true, + resolveConditional: false, + sort: true, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(4).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + declareRawModules: false, + renderer: false, + resolve: true, + resolveConditional: false, + sort: true + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + renderer: true, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(5).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(6).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); +}); + +test.serial("generateLibraryPreload for sap.ui.core with old specVersion defined (/w ui5loader.js)", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob, secondByGlob, + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/ui5loader.js")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + secondByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + getSpecVersion: () => { + return { + toString: () => "0.1", + gte: sinon.stub().withArgs("4.0").returns(false), + lte: sinon.stub().withArgs("2.0").returns(true), + lt: sinon.stub().withArgs("4.0").returns(true), + }; + }, + getVersion: () => "1.120.0" + }; + + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + taskUtil.getTag + .withArgs(sinon.match.any, taskUtil.STANDARD_TAGS.HasDebugVariant) + .returns(true); + await generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core", + // Should be ignored for hardcoded sap.ui.core bundle configuration + excludes: ["sap/ui/core/**"] + } + }); + + t.is(workspace.byGlob.callCount, 3, + "workspace.byGlob should have been called three times"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(2).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); + + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 7 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + declareModules: false, + declareRawModules: false, + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + async: false + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-dbg.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-nojQuery.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + declareRawModules: false, + renderer: false, + resolveConditional: false, + sort: true, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(3).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap-ui-core-nojQuery-dbg.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(4).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + resolve: true, + resolveConditional: false, + sort: true, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + renderer: true, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(5).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(6).args, [{ + options: { + allowStringBundling: true, + bundleDefinition: { + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); +}); + +test.serial("generateLibraryPreload for sap.ui.core with own bundle configuration (w/o ui5loader.js)", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + // A newer specVersion is the indicator that the hardcoded bundle config should be skipped + getSpecVersion: () => { + return { + toString: () => "2.4", + gte: sinon.stub().withArgs("4.0").returns(false), + lte: sinon.stub().withArgs("2.0").returns(false), + lt: sinon.stub().withArgs("4.0").returns(false), + }; + }, + getVersion: () => "1.120.0" + }; + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + await generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core" + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(workspace.byGlob.callCount, 2, + "workspace.byGlob should have been called twice"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); +}); + +test.serial("generateLibraryPreload for sap.ui.core with own bundle configuration (/w ui5loader.js)", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/ui5loader.js")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + // A newer specVersion is the indicator that the hardcoded bundle config should be skipped + getSpecVersion: () => { + return { + toString: () => "2.6", + gte: sinon.stub().withArgs("4.0").returns(false), + lte: sinon.stub().withArgs("2.0").returns(false), + lt: sinon.stub().withArgs("4.0").returns(false), + }; + }, + getVersion: () => "1.120.0" + }; + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + taskUtil.getTag + .withArgs(resources[0], taskUtil.STANDARD_TAGS.HasDebugVariant) + .returns(true); + await generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core" + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "1.120.0" + }, + resources + }]); + + t.is(workspace.byGlob.callCount, 2, + "workspace.byGlob should have been called twice"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); +}); + +test.serial("Error: Failed to resolve non-debug name", async (t) => { + const { + generateLibraryPreload, + workspace, dependencies + } = t.context; + const resources = [ + {getPath: sinon.stub().returns("/resources/resource-tagged-as-debug-variant.js")} + ]; + t.context.workspace.byGlob.onFirstCall().resolves(resources); + t.context.workspace.byGlob.onSecondCall().resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + getSpecVersion: () => { + return { + toString: () => "0.1", + gte: sinon.stub().withArgs("4.0").returns(false), + lte: sinon.stub().withArgs("2.0").returns(true), + lt: sinon.stub().withArgs("4.0").returns(false) + }; + }, + getVersion: () => "1.120.0" + }; + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + taskUtil.getTag + .withArgs(resources[0], taskUtil.STANDARD_TAGS.IsDebugVariant) + .returns(true); + + await t.throwsAsync(generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core", + // Should be ignored for hardcoded sap.ui.core bundle configuration + excludes: ["sap/ui/core/**"] + } + }), { + message: "Failed to resolve non-debug name for /resources/resource-tagged-as-debug-variant.js" + }); +}); + +test.serial("generateLibraryPreload with excludes", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]); + + await generateLibraryPreload({ + workspace, + dependencies, + options: { + projectName: "Test Library", + excludes: [ + "my/lib/thirdparty/", + "!my/lib/thirdparty/NotExcluded.js" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "my/lib/library-preload.js", + sections: [ + { + filters: [ + "my/lib/", + "my/lib/**/manifest.json", + "!my/lib/**/*-preload.js", + "!my/lib/designtime/", + "!my/lib/**/*.designtime.js", + "!my/lib/**/*.support.js", + + // via excludes option + "!my/lib/thirdparty/", + "+my/lib/thirdparty/NotExcluded.js" + ], + mode: "preload", + renderer: true, + resolve: false, + resolveConditional: false, + declareRawModules: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(workspace.byGlob.callCount, 2, + "workspace.byGlob should have been called twice"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); +}); + +test.serial("generateLibraryPreload with invalid excludes", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob, log + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]; + firstByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/my/lib/.library")} + ]); + + await generateLibraryPreload({ + workspace, + dependencies, + options: { + projectName: "Test Library", + excludes: [ + "!**/foo/", + "!my/other/lib/" + ] + } + }); + + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: undefined, + bundleDefinition: { + name: "my/lib/library-preload.js", + sections: [ + { + filters: [ + "my/lib/", + "my/lib/**/manifest.json", + "!my/lib/**/*-preload.js", + "!my/lib/designtime/", + "!my/lib/**/*.designtime.js", + "!my/lib/**/*.support.js" + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + } + }, + resources + }]); + + t.is(log.warn.callCount, 2, "log.warn should be called twice"); + t.deepEqual(log.warn.getCall(0).args, [ + "Configured preload exclude contains invalid re-include: !**/foo/. " + + "Re-includes must start with the library's namespace my/lib" + ]); + t.deepEqual(log.warn.getCall(1).args, [ + "Configured preload exclude contains invalid re-include: !my/other/lib/. " + + "Re-includes must start with the library's namespace my/lib" + ]); + + t.is(log.verbose.callCount, 0, "log.verbose should not be called"); + t.is(log.error.callCount, 0, "log.error should not be called"); +}); + +test.serial("generateLibraryPreload for sap.ui.core (/w ui5loader.js), UI5 Version >= 2", async (t) => { + const { + generateLibraryPreload, moduleBundlerStub, + workspace, dependencies, firstByGlob, secondByGlob, + } = t.context; + + const resources = [ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")}, + {getPath: sinon.stub().returns("/resources/ui5loader.js")}, + {getPath: sinon.stub().returns("/resources/sap-ui-core.js")} + ]; + firstByGlob.resolves(resources); + secondByGlob.resolves(resources); + + workspace.byGlob.resolves([ + {getPath: sinon.stub().returns("/resources/sap/ui/core/.library")} + ]); + + const coreProject = { + getSpecVersion: () => { + return { + gte: sinon.stub().withArgs("4.0").returns(false), + toString: () => "4.0", + lte: sinon.stub().withArgs("2.0").returns(true), + lt: sinon.stub().withArgs("4.0").returns(false) + }; + }, + getVersion: () => "2.0.0" + }; + const taskUtil = { + getTag: sinon.stub().returns(false), + getProject: () => coreProject, + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: () => workspace + } + }; + await generateLibraryPreload({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "sap.ui.core", + // Should be ignored for hardcoded sap.ui.core bundle configuration + excludes: ["sap/ui/core/**"] + } + }); + + t.is(workspace.byGlob.callCount, 3, + "workspace.byGlob should have been called three times"); + t.deepEqual(workspace.byGlob.getCall(0).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(1).args, ["/**/*.{js,json,xml,html,properties,library,js.map}"], + "workspace.byGlob should have been called with expected pattern"); + t.deepEqual(workspace.byGlob.getCall(2).args, ["/resources/**/.library"], + "workspace.byGlob should have been called with expected pattern"); + + t.is(taskUtil.getTag.callCount, 2, "TaskUtil#getTag got called two times"); + t.is(taskUtil.getTag.getCall(0).args[0], resources[2], + "TaskUtil#getTag got called with expected resource on first call"); + t.is(taskUtil.getTag.getCall(0).args[1], "", + "TaskUtil#getTag got called with expected tag on first call"); + t.is(taskUtil.getTag.getCall(1).args[0], resources[1], + "TaskUtil#getTag got called with expected resource on second call"); + t.is(taskUtil.getTag.getCall(1).args[1], "", + "TaskUtil#getTag got called with expected tag on second call"); + + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 7 times"); + t.deepEqual(moduleBundlerStub.getCall(0).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap-ui-core.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + declareRawModules: false, + renderer: false, + resolveConditional: false + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + declareRawModules: false, + renderer: false, + resolveConditional: false, + sort: true + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(1).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap-ui-core-dbg.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(2).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap-ui-core-nojQuery.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "preload", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + decorateBootstrapModule: true, + addTryCatchRestartWrapper: true + }, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(3).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap-ui-core-nojQuery-dbg.js", + sections: [ + { + mode: "provided", + filters: [ + "jquery-ui-core.js", + "jquery-ui-datepicker.js", + "jquery-ui-position.js", + "sap/ui/thirdparty/jquery.js", + "sap/ui/thirdparty/jquery/*", + "sap/ui/thirdparty/jqueryui/*" + ], + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + }, + { + filters: [ + "ui5loader-autoconfig.js" + ], + mode: "raw", + resolve: true, + sort: true, + declareModules: false, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + mode: "require", + filters: [ + "sap/ui/core/Core.js" + ], + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: false, + decorateBootstrapModule: false, + addTryCatchRestartWrapper: false + }, + moduleNameMapping: {}, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(4).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.js", + sections: [ + { + filters: [ + "ui5loader-autoconfig.js", + "sap/ui/core/Core.js", + ], + mode: "provided", + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + }, + { + filters: [ + "sap/ui/core/", + "sap/ui/core/**/manifest.json", + "!sap/ui/core/**/*-preload.js", + "!sap/ui/core/designtime/", + "!sap/ui/core/**/*.designtime.js", + "!sap/ui/core/**/*.support.js", + + "!sap/ui/core/cldr/", + "*.js", + "sap/base/", + "sap/ui/base/", + "sap/ui/dom/", + "sap/ui/events/", + "sap/ui/model/", + "sap/ui/security/", + "sap/ui/util/", + "sap/ui/Global.js", + "sap/ui/thirdparty/crossroads.js", + "sap/ui/thirdparty/caja-html-sanitizer.js", + "sap/ui/thirdparty/hasher.js", + "sap/ui/thirdparty/signals.js", + "sap/ui/thirdparty/jquery-mobile-custom.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-core.js", + "sap/ui/thirdparty/jqueryui/jquery-ui-position.js", + "!sap-ui-*.js", + "!sap/ui/core/support/", + "!sap/ui/core/plugin/DeclarativeSupport.js", + "!sap/ui/core/plugin/LessSupport.js", + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: true, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true + }, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(5).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: true, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); + t.deepEqual(moduleBundlerStub.getCall(6).args, [{ + options: { + allowStringBundling: false, + bundleDefinition: { + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + resolve: false, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false, + } + ] + }, + bundleOptions: { + optimize: false, + ignoreMissingModules: true, + skipIfEmpty: true + }, + targetUi5CoreVersion: "2.0.0" + }, + resources + }]); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js new file mode 100644 index 00000000000..0427a8bba59 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js @@ -0,0 +1,264 @@ +import test from "ava"; +import path from "node:path"; +import sinon from "sinon"; +import {directoryDeepEqual, fileEqual, findFiles} from "../../../utils/fshelper.js"; +import {graphFromObject} from "@ui5/project/graph"; +import * as taskRepository from "../../../../lib/tasks/taskRepository.js"; + +const __dirname = import.meta.dirname; +const applicationBPath = path.join(__dirname, "..", "..", "..", "fixtures", "application.b"); +const applicationNPath = path.join(__dirname, "..", "..", "..", "fixtures", "application.n"); +const sapUiCorePath = path.join(__dirname, "..", "..", "..", "fixtures", "sap.ui.core"); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("integration: build application.b standalone", async (t) => { + const destPath = "./test/tmp/build/application.b/standalone"; + const expectedPath = "./test/expected/build/application.b/standalone"; + const excludedTasks = ["*"]; + const includedTasks = ["minify", "generateStandaloneAppBundle"]; + + const graph = await graphFromObject({ + dependencyTree: applicationBTree + }); + + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks, + includedTasks + }); + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile); + })); +}); + +const applicationBTree = { + "id": "application.b", + "version": "1.0.0", + "path": applicationBPath, + "dependencies": [ + { + "id": "sap.ui.core", + "version": "1.0.0", + "path": sapUiCorePath, + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "sap.ui.core", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + } + } + } + }, + }, + { + "id": "library.d", + "version": "1.0.0", + "path": path.join(applicationBPath, "..", "library.d"), + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.d", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src", + "test": "main/test" + } + } + } + }, + }, + { + "id": "library.a", + "version": "1.0.0", + "path": path.join(applicationBPath, "..", "collection", "library.a"), + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.a", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + }, + }, + { + "id": "library.b", + "version": "1.0.0", + "path": path.join(applicationBPath, "..", "collection", "library.b"), + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.b", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + } + }, + { + "id": "library.c", + "version": "1.0.0", + "path": path.join(applicationBPath, "..", "collection", "library.c"), + "dependencies": [], + "configuration": { + "specVersion": "2.0", + "type": "library", + "metadata": { + "name": "library.c", + "copyright": "${copyright}" + }, + "resources": { + "configuration": { + "paths": { + "src": "src", + "test": "test" + } + } + } + }, + } + ], + "configuration": { + "builder": {}, + "specVersion": "2.0", + "type": "application", + "metadata": { + "name": "application.b" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + }, + "propertiesFileSourceEncoding": "ISO-8859-1" + } + } + }, +}; + +test("integration: build application.n standalone without enabled string bundling", async (t) => { + const destPath = "./test/tmp/build/application.n/dest"; + const expectedPath = "./test/expected/build/application.n/dest"; + const excludedTasks = ["*"]; + const includedTasks = ["minify", "generateStandaloneAppBundle"]; + + const graph = await graphFromObject({ + dependencyTree: applicationNTree + }); + + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks, + includedTasks + }); + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath, "Result directory structure correct"); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile, "Correct file content"); + })); +}); + +const applicationNTree = { + "id": "application.n", + "version": "1.0.0", + "path": applicationNPath, + "dependencies": [], + "configuration": { + "specVersion": "4.0", + "type": "application", + "metadata": { + "name": "application.n" + } + } +}; + +test("integration: build application.n standalone with enabled string bundling", async (t) => { + const destPath = "./test/tmp/build/application.n/legacy"; + const expectedPath = "./test/expected/build/application.n/legacy"; + const excludedTasks = ["*"]; + const includedTasks = ["minify", "generateStandaloneAppBundle"]; + + const graph = await graphFromObject({ + dependencyTree: applicationNTreeLegacy + }); + + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks, + includedTasks + }); + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath, "Result directory structure correct"); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + await fileEqual(t, destFile, expectedFile, "Correct file content"); + })); +}); + +const applicationNTreeLegacy = { + "id": "application.n", + "version": "1.0.0", + "path": applicationNPath, + "dependencies": [], + "configuration": { + "specVersion": "3.1", + "type": "application", + "metadata": { + "name": "application.n" + } + } +}; diff --git a/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.js b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.js new file mode 100644 index 00000000000..20bf7b73e47 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.js @@ -0,0 +1,596 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + // Stubbing processors/bundlers/moduleBundler + t.context.moduleBundlerStub = sinon.stub(); + t.context.moduleBundlerStub.resolves([{bundle: "I am a resource", sourceMap: "I am a source map"}]); + + t.context.createFilterReaderStub = sinon.stub().callsFake((params) => { + return params.reader; + }); + + const project = { + getSpecVersion() { + return { + toString: () => "0.1", + lt: sinon.stub().withArgs("4.0").returns(true), + gte: sinon.stub().withArgs("4.0").returns(false), + }; + }, + getVersion: () => "1.120.0" + }; + t.context.taskUtil = { + getProject: sinon.stub().returns(project), + getTag: sinon.stub().returns(false), + setTag: sinon.stub(), + clearTag: sinon.stub(), + STANDARD_TAGS: { + HasDebugVariant: "", + IsDebugVariant: "", + OmitFromBuildResult: "" + }, + resourceFactory: { + createFilterReader: t.context.createFilterReaderStub + } + }; + t.context.generateStandaloneAppBundle = + await esmock("../../../../lib/tasks/bundlers/generateStandaloneAppBundle.js", { + "../../../../lib/processors/bundlers/moduleBundler.js": t.context.moduleBundlerStub + }); +}); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +function createDummyResource(id) { + return { + getPath: function() { + return "/resources/ponyPath" + id; + } + }; +} + +test.serial("execute module bundler and write results", async (t) => { + const {generateStandaloneAppBundle} = t.context; + let dummyResourceId = 0; + const dummyReaderWriter = { + _byGlob: async function() { + return [createDummyResource(dummyResourceId++), createDummyResource(dummyResourceId++)]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + options: { + projectName: "some.project.name", + projectNamespace: "some/project/namespace" + } + }; + await generateStandaloneAppBundle(params); + + t.is(t.context.moduleBundlerStub.callCount, 2); + + const {resources, options} = t.context.moduleBundlerStub.getCall(0).args[0]; + t.is(resources.length, 4, "moduleBundler got supplied with 4 resources"); + t.deepEqual(options.bundleDefinition.sections[0].filters, [ + "jquery.sap.global.js" + ], "Correct filter in first bundle definition section"); + t.deepEqual(options.bundleDefinition.sections[1].filters, [ + "some/project/namespace/", + "some/project/namespace/**/manifest.json", + "some/project/namespace/changes/changes-bundle.json", + "some/project/namespace/changes/flexibility-bundle.json", + "!some/project/namespace/test/", + "sap/ui/core/Core.js" + ], "Correct filter in second bundle definition section"); + t.deepEqual(options.bundleDefinition.defaultFileTypes, [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties" + ], "Correct default file types in bundle definition"); +}); + +test.serial("execute module bundler and write results without namespace", async (t) => { + const {generateStandaloneAppBundle} = t.context; + let dummyResourceId = 0; + const dummyReaderWriter = { + _byGlob: async function() { + return [createDummyResource(dummyResourceId++), createDummyResource(dummyResourceId++)]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + options: { + projectName: "some.project.name" + } + }; + await generateStandaloneAppBundle(params); + + t.is(t.context.moduleBundlerStub.callCount, 2); + + const {resources, options} = t.context.moduleBundlerStub.getCall(0).args[0]; + t.is(resources.length, 4, "moduleBundler got supplied with 4 resources"); + t.deepEqual(options.bundleDefinition.sections[0].filters, [ + "jquery.sap.global.js" + ], "Correct filter in first bundle definition section"); + t.deepEqual(options.bundleDefinition.sections[1].filters, [ + "/", + "/**/manifest.json", + "/changes/changes-bundle.json", + "/changes/flexibility-bundle.json", + "!/test/", + "sap/ui/core/Core.js" + ], "Correct filter in second bundle definition section"); +}); + + +test.serial("execute module bundler and write results in evo mode", async (t) => { + const {generateStandaloneAppBundle} = t.context; + let dummyResourceId = 0; + + const ui5LoaderDummyResource = { + getPath: function() { + return "/resources/ui5loader.js"; // Triggers evo mode + } + }; + const dummyReaderWriter = { + _byGlob: async function() { + if (dummyResourceId === 0) { + return [ui5LoaderDummyResource, createDummyResource(dummyResourceId++)]; + } + return [createDummyResource(dummyResourceId++), createDummyResource(dummyResourceId++)]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + options: { + projectName: "some.project.name", + projectNamespace: "some/project/namespace" + } + }; + await generateStandaloneAppBundle(params); + + t.is(t.context.moduleBundlerStub.callCount, 2); + + const {resources, options} = t.context.moduleBundlerStub.getCall(0).args[0]; + t.is(resources.length, 4, "moduleBundler got supplied with 4 resources"); + t.deepEqual(options.bundleDefinition.sections[0].filters, [ + "ui5loader-autoconfig.js" + ], "Evo mode active - Correct filter in first bundle definition section"); + t.deepEqual(options.bundleDefinition.sections[1].filters, [ + "some/project/namespace/", + "some/project/namespace/**/manifest.json", + "some/project/namespace/changes/changes-bundle.json", + "some/project/namespace/changes/flexibility-bundle.json", + "!some/project/namespace/test/", + "sap/ui/core/Core.js" + ], "Correct filter in second bundle definition section"); +}); + +test.serial("execute module bundler with taskUtil", async (t) => { + const {generateStandaloneAppBundle, taskUtil, createFilterReaderStub, moduleBundlerStub} = t.context; + + const dummyResource1 = createDummyResource("1.js"); + const dummyResource2 = createDummyResource("2-dbg.js"); + const dummyResource3 = createDummyResource("3.js"); + const dummyResource4 = createDummyResource("4-dbg.js"); + + taskUtil.getTag.withArgs(dummyResource1, taskUtil.STANDARD_TAGS.HasDebugVariant).returns(true); + taskUtil.getTag.withArgs(dummyResource2, taskUtil.STANDARD_TAGS.IsDebugVariant).returns(true); + + const ui5LoaderDummyResource = { + getPath: function() { + return "/resources/ui5loader.js"; // Triggers evo mode + } + }; + const dummyReaderWriter = { + _byGlob: async function() { + return [ + ui5LoaderDummyResource, + dummyResource1, + dummyResource2, + dummyResource3, + dummyResource4, + ]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + taskUtil, + options: { + projectName: "some.project.name", + projectNamespace: "some/project/namespace" + } + }; + await generateStandaloneAppBundle(params); + + t.is(taskUtil.getTag.callCount, 5, "TaskUtil#getTag got called six times"); + + t.is(createFilterReaderStub.callCount, 2, "Two filter readers have been created"); + t.is(createFilterReaderStub.getCall(0).args[0].reader.getName(), + "generateStandaloneAppBundle - prioritize workspace over dependencies: some.project.name", + "Correct reader argument on first createFilterReader call"); + + taskUtil.getTag.reset(); + // Execute first filter-reader callback and test side effect on taskUtil + const filterReaderCallbackRes1 = createFilterReaderStub.getCall(0).args[0].callback("resource"); + t.is(taskUtil.getTag.callCount, 1, "TaskUtil#getTag got called once by callback"); + t.deepEqual(taskUtil.getTag.getCall(0).args, ["resource", ""], + "TaskUtil getTag got called with correct argument"); + t.is(filterReaderCallbackRes1, true, "First filter-reader callback returned expected value"); + + t.is(createFilterReaderStub.getCall(1).args[0].reader.getName(), + "generateStandaloneAppBundle - prioritize workspace over dependencies: some.project.name", + "Correct reader argument on first createFilterReader call"); + + taskUtil.getTag.reset(); + // Execute second filter-callback and test side effect on taskUtil + const filterReaderCallbackRes2 = createFilterReaderStub.getCall(1).args[0].callback("resource"); + t.is(taskUtil.getTag.callCount, 1, "TaskUtil#getTag got called once by callback"); + t.deepEqual(taskUtil.getTag.getCall(0).args, ["resource", ""], + "TaskUtil getTag got called with correct argument"); + t.is(filterReaderCallbackRes2, true, "First filter-reader callback returned expected value"); + + + t.is(moduleBundlerStub.callCount, 2); + + t.is(moduleBundlerStub.getCall(0).args.length, 1); + t.deepEqual(moduleBundlerStub.getCall(0).args[0].options, { + allowStringBundling: true, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ], + name: "sap-ui-custom.js", + sections: [ + { + declareModules: false, + filters: [ + "ui5loader-autoconfig.js", + ], + mode: "raw", + resolve: true, + sort: true, + declareRawModules: false, + renderer: false, + resolveConditional: false + }, + { + filters: [ + "some/project/namespace/", + "some/project/namespace/**/manifest.json", + "some/project/namespace/changes/changes-bundle.json", + "some/project/namespace/changes/flexibility-bundle.json", + "!some/project/namespace/test/", + "sap/ui/core/Core.js", + ], + mode: "preload", + renderer: true, + resolve: true, + resolveConditional: true, + declareRawModules: false, + sort: true + }, + { + filters: [ + "sap/ui/core/Core.js", + ], + mode: "require", + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + async: false + }, + ], + }, + targetUi5CoreVersion: "1.120.0" + }); + + t.is(moduleBundlerStub.getCall(1).args.length, 1); + t.deepEqual(moduleBundlerStub.getCall(1).args[0].options, { + allowStringBundling: true, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ], + name: "sap-ui-custom-dbg.js", + sections: [ + { + declareModules: false, + filters: [ + "ui5loader-autoconfig.js", + ], + mode: "raw", + resolve: true, + sort: true, + resolveConditional: false, + renderer: false, + declareRawModules: false + }, + { + filters: [ + "sap/ui/core/Core.js", + ], + mode: "require", + renderer: false, + resolve: false, + resolveConditional: false, + sort: true, + declareRawModules: false, + async: false + }, + ], + }, + bundleOptions: { + optimize: false + }, + moduleNameMapping: { + "/resources/ponyPath2-dbg.js": "ponyPath2.js" + }, + targetUi5CoreVersion: "1.120.0" + }); +}); + +test.serial("execute module bundler with taskUtil, UI5 Version >= 2", async (t) => { + const {generateStandaloneAppBundle, taskUtil, createFilterReaderStub, moduleBundlerStub} = t.context; + + const dummyResource1 = createDummyResource("1.js"); + const dummyResource2 = createDummyResource("2-dbg.js"); + const dummyResource3 = createDummyResource("3.js"); + const dummyResource4 = createDummyResource("4-dbg.js"); + + taskUtil.getProject = () => { + return { + getVersion: () => "2.0.0", + getSpecVersion: () => { + return { + lt: sinon.stub().withArgs("4.0").returns(false), + gte: sinon.stub().withArgs("4.0").returns(true) + }; + } + }; + }; + + taskUtil.getTag.withArgs(dummyResource1, taskUtil.STANDARD_TAGS.HasDebugVariant).returns(true); + taskUtil.getTag.withArgs(dummyResource2, taskUtil.STANDARD_TAGS.IsDebugVariant).returns(true); + + const ui5LoaderDummyResource = { + getPath: function() { + return "/resources/ui5loader.js"; // Triggers evo mode + } + }; + const dummyReaderWriter = { + _byGlob: async function() { + return [ + ui5LoaderDummyResource, + dummyResource1, + dummyResource2, + dummyResource3, + dummyResource4, + ]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + taskUtil, + options: { + projectName: "some.project.name", + projectNamespace: "some/project/namespace" + } + }; + await generateStandaloneAppBundle(params); + + t.is(taskUtil.getTag.callCount, 5, "TaskUtil#getTag got called six times"); + + t.is(createFilterReaderStub.callCount, 2, "Two filter readers have been created"); + t.is(createFilterReaderStub.getCall(0).args[0].reader.getName(), + "generateStandaloneAppBundle - prioritize workspace over dependencies: some.project.name", + "Correct reader argument on first createFilterReader call"); + + taskUtil.getTag.reset(); + // Execute first filter-reader callback and test side effect on taskUtil + const filterReaderCallbackRes1 = createFilterReaderStub.getCall(0).args[0].callback("resource"); + t.is(taskUtil.getTag.callCount, 1, "TaskUtil#getTag got called once by callback"); + t.deepEqual(taskUtil.getTag.getCall(0).args, ["resource", ""], + "TaskUtil getTag got called with correct argument"); + t.is(filterReaderCallbackRes1, true, "First filter-reader callback returned expected value"); + + t.is(createFilterReaderStub.getCall(1).args[0].reader.getName(), + "generateStandaloneAppBundle - prioritize workspace over dependencies: some.project.name", + "Correct reader argument on first createFilterReader call"); + + taskUtil.getTag.reset(); + // Execute second filter-callback and test side effect on taskUtil + const filterReaderCallbackRes2 = createFilterReaderStub.getCall(1).args[0].callback("resource"); + t.is(taskUtil.getTag.callCount, 1, "TaskUtil#getTag got called once by callback"); + t.deepEqual(taskUtil.getTag.getCall(0).args, ["resource", ""], + "TaskUtil getTag got called with correct argument"); + t.is(filterReaderCallbackRes2, true, "First filter-reader callback returned expected value"); + + + t.is(moduleBundlerStub.callCount, 2); + + t.is(moduleBundlerStub.getCall(0).args.length, 1); + t.deepEqual(moduleBundlerStub.getCall(0).args[0].options, { + allowStringBundling: false, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ], + name: "sap-ui-custom.js", + sections: [ + { + declareModules: false, + filters: [ + "ui5loader-autoconfig.js", + ], + mode: "raw", + resolve: true, + sort: true, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + filters: [ + "some/project/namespace/", + "some/project/namespace/**/manifest.json", + "some/project/namespace/changes/changes-bundle.json", + "some/project/namespace/changes/flexibility-bundle.json", + "!some/project/namespace/test/", + "sap/ui/core/Core.js", + ], + mode: "preload", + renderer: true, + resolve: true, + resolveConditional: true, + declareRawModules: false, + sort: true + }, + { + filters: [ + "sap/ui/core/Core.js", + ], + mode: "require", + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + }, + ], + }, + targetUi5CoreVersion: "2.0.0" + }); + + t.is(moduleBundlerStub.getCall(1).args.length, 1); + t.deepEqual(moduleBundlerStub.getCall(1).args[0].options, { + allowStringBundling: false, + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ], + name: "sap-ui-custom-dbg.js", + sections: [ + { + declareModules: false, + filters: [ + "ui5loader-autoconfig.js", + ], + mode: "raw", + resolve: true, + sort: true, + declareRawModules: false, + renderer: false, + resolveConditional: false, + }, + { + filters: [ + "sap/ui/core/Core.js", + ], + mode: "require", + declareRawModules: false, + renderer: false, + resolve: false, + resolveConditional: false, + sort: true + }, + ], + }, + bundleOptions: { + optimize: false + }, + moduleNameMapping: { + "/resources/ponyPath2-dbg.js": "ponyPath2.js" + }, + targetUi5CoreVersion: "2.0.0" + }); +}); + +test.serial("Error: Failed to resolve non-debug name", async (t) => { + // NOTE: This scenario is not expected to happen as the "minify" task sets the IsDebugVariant tag + // only for resources that adhere to the debug file name pattern + const {generateStandaloneAppBundle, taskUtil} = t.context; + const dummyResource1 = createDummyResource("1.js"); + taskUtil.getTag.withArgs(dummyResource1, taskUtil.STANDARD_TAGS.IsDebugVariant).returns(true); + + const dummyReaderWriter = { + _byGlob: async function() { + return [ + dummyResource1, + ]; + }, + write: function() {} + }; + sinon.stub(dummyReaderWriter, "write").resolves(); + const params = { + workspace: dummyReaderWriter, + dependencies: dummyReaderWriter, + taskUtil, + options: { + projectName: "some.project.name", + projectNamespace: "some/project/namespace" + } + }; + + await t.throwsAsync(generateStandaloneAppBundle(params), { + message: "Failed to resolve non-debug name for /resources/ponyPath1.js" + }); +}); diff --git a/packages/builder/test/lib/tasks/bundlers/utils/createModuleNameMapping.js b/packages/builder/test/lib/tasks/bundlers/utils/createModuleNameMapping.js new file mode 100644 index 00000000000..b7c4f7d11f9 --- /dev/null +++ b/packages/builder/test/lib/tasks/bundlers/utils/createModuleNameMapping.js @@ -0,0 +1,51 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import createModuleNameMapping from "../../../../../lib/tasks/bundlers/utils/createModuleNameMapping.js"; + +test.beforeEach((t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + t.context.taskUtil = { + getTag: sinon.stub(), + STANDARD_TAGS: { + IsDebugVariant: "🦄", + }, + }; +}); +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test("createModuleNameMapping", (t) => { + const {taskUtil} = t.context; + taskUtil.getTag.callsFake((resource) => { + if (resource.getPath() === "/resources/Module.js") { + return false; + } + // All but the first shall be assumed debug variants + return true; + }); + const resources = [{ + getPath: () => "/resources/Module.js" + }, { + getPath: () => "/resources/Module-dbg.js" + }, { + getPath: () => "/resources/Module-dbg.js.map" + }, { + getPath: () => "/resources/Module-dbg-dbg.js" + }, { + getPath: () => "/resources/Module-dbg-dbg.js.map" + }, { + getPath: () => "/resources/Module.xml" + }, { + getPath: () => "/resources/Module.css" + }]; + const res = createModuleNameMapping({ + resources, + taskUtil + }); + + t.deepEqual(res, { + "/resources/Module-dbg-dbg.js": "Module-dbg.js", + "/resources/Module-dbg.js": "Module.js" + }, "Expected module name mapping"); +}); diff --git a/packages/builder/test/lib/tasks/enhanceManifest.js b/packages/builder/test/lib/tasks/enhanceManifest.js new file mode 100644 index 00000000000..25d30706708 --- /dev/null +++ b/packages/builder/test/lib/tasks/enhanceManifest.js @@ -0,0 +1,348 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; + +function createWorkspace() { + return createAdapter({ + virBasePath: "/", + project: { + getName: () => "test.lib", + getVersion: () => "2.0.0", + } + }); +} + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.log = { + verbose: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + }; + + t.context.manifestEnhancerStub = sinon.stub(); + t.context.fsInterfaceStub = sinon.stub().returns("fs interface"); + t.context.enhanceManifest = await esmock("../../../lib/tasks/enhanceManifest.js", { + "@ui5/logger": { + getLogger: sinon.stub().withArgs("builder:tasks:enhanceManifest").returns(t.context.log) + }, + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "../../../lib/processors/manifestEnhancer": t.context.manifestEnhancerStub, + }); + t.context.workspace = createWorkspace(); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("Transforms single manifest.json resource", async (t) => { + const {enhanceManifest, log} = t.context; + + t.plan(6); + + const resource = createResource({ + path: "/resources/sap/ui/demo/app/manifest.json", + string: `{ +"_version": "1.58.0", +"sap.app": { + "id": "sap.ui.demo.app", + "type": "application", + "title": "{{title}}" +} +`, + project: t.context.workspace._project + }); + + const workspace = { + byGlob: (actualPath) => { + t.is(actualPath, "/resources/sap/ui/demo/app/**/manifest.json", + "Reads all manifest.json files"); + return Promise.resolve([resource]); + }, + write: (actualResource) => { + t.deepEqual(actualResource, resource, + "Expected resource is written back to workspace"); + } + }; + + t.context.manifestEnhancerStub.returns([resource]); + + await enhanceManifest({ + workspace, + options: { + projectNamespace: "sap/ui/demo/app" + } + }); + + t.is(t.context.manifestEnhancerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.manifestEnhancerStub.calledWithExactly({ + resources: [resource], + fs: "fs interface" + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); + t.true(log.error.notCalled, "No errors should be logged"); +}); + +test.serial("Transforms all manifest.json resources", async (t) => { + const {enhanceManifest, log} = t.context; + + t.plan(6); + + const resourceLib = createResource({ + path: "/resources/sap/ui/demo/lib/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "library" + }, + "sap.ui5": { + "library": { + "i18n": { + "bundleUrl": "i18n/i18n.properties" + } + } + } +}`, + project: t.context.workspace._project + }); + + const resourceReuseComp1 = createResource({ + path: "/resources/sap/ui/demo/lib/comp1/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "component" + }, + "sap.ui5": { + "models": { + "i18n": { + "bundleUrl": "i18n/i18n.properties" + } + } + } +}`, + project: t.context.workspace._project + }); + + const resourceReuseComp2 = createResource({ + path: "/resources/sap/ui/demo/lib/comp2/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "component" + }, + "sap.ui5": { + "models": { + "i18n": { + "bundleUrl": "i18n/i18n.properties", + "supportedLocales": ["fr", "en"] + } + } + } +}`, + project: t.context.workspace._project + }); + + const workspace = { + byGlob: () => { + return Promise.resolve([resourceLib, resourceReuseComp1, resourceReuseComp2]); + }, + write: (actualResource) => { + const path = actualResource.getPath(); + let expectedResource; + if (path === "/resources/sap/ui/demo/lib/manifest.json") { + expectedResource = resourceLib; + } else if (path === "/resources/sap/ui/demo/lib/comp1/manifest.json") { + expectedResource = resourceReuseComp1; + } else if (path === "/resources/sap/ui/demo/lib/comp2/manifest.json") { + t.fail("Resoure should be written, because it was not returned by the processor"); + } else { + t.fail("No other resoure should be written"); + } + t.deepEqual(actualResource, expectedResource, + "Expected resource is written back to workspace"); + } + }; + + t.context.manifestEnhancerStub.returns([resourceLib, resourceReuseComp1]); + + await enhanceManifest({ + workspace, + options: { + projectNamespace: "sap/ui/demo/lib" + } + }); + + t.is(t.context.manifestEnhancerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.manifestEnhancerStub.calledWithExactly({ + resources: [resourceLib, resourceReuseComp1, resourceReuseComp2], + fs: "fs interface" + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); + t.true(log.error.notCalled, "No errors should be logged"); +}); + +test.serial("Transforms multiple manifest.json resources", async (t) => { + const {enhanceManifest, log} = t.context; + + t.plan(7); + + const resourceLib = createResource({ + path: "/resources/sap/ui/demo/lib/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "library" + }, + "sap.ui5": { + "library": { + "i18n": { + "bundleUrl": "i18n/i18n.properties" + } + } + } +}`, + project: t.context.workspace._project + }); + + const resourceReuseComp1 = createResource({ + path: "/resources/sap/ui/demo/lib/comp1/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "component" + }, + "sap.ui5": { + "models": { + "i18n": { + "bundleUrl": "i18n/i18n.properties" + } + } + } +}`, + project: t.context.workspace._project + }); + + const resourceReuseComp2 = createResource({ + path: "/resources/sap/ui/demo/lib/comp2/manifest.json", + string: `{ + "_version": "1.58.0", + "sap.app": { + "id": "sap.ui.demo.lib", + "type": "component" + }, + "sap.ui5": { + "models": { + "i18n": { + "bundleUrl": "i18n/i18n.properties" + } + } + } +}`, + project: t.context.workspace._project + }); + + const workspace = { + byGlob: () => { + return Promise.resolve([resourceLib, resourceReuseComp1, resourceReuseComp2]); + }, + write: (actualResource) => { + const path = actualResource.getPath(); + let expectedResource; + if (path === "/resources/sap/ui/demo/lib/manifest.json") { + expectedResource = resourceLib; + } else if (path === "/resources/sap/ui/demo/lib/comp1/manifest.json") { + expectedResource = resourceReuseComp1; + } else if (path === "/resources/sap/ui/demo/lib/comp2/manifest.json") { + expectedResource = resourceReuseComp2; + } else { + t.fail("No other resoure should be written"); + } + t.deepEqual(actualResource, expectedResource, + "Expected resource is written back to workspace"); + } + }; + + t.context.manifestEnhancerStub.returns([resourceLib, resourceReuseComp1, resourceReuseComp2]); + + await enhanceManifest({ + workspace, + options: { + projectNamespace: "sap/ui/demo/lib" + } + }); + + t.is(t.context.manifestEnhancerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.manifestEnhancerStub.calledWithExactly({ + resources: [resourceLib, resourceReuseComp1, resourceReuseComp2], + fs: "fs interface" + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); + t.true(log.error.notCalled, "No errors should be logged"); +}); + +test.serial("Should not rewrite the manifest.json if no changes were made", async (t) => { + const {enhanceManifest, log} = t.context; + + t.plan(5); + + const resource = createResource({ + path: "/resources/sap/ui/demo/app/manifest.json", + string: `{ +"_version": "1.58.0", +"sap.app": { + "id": "sap.ui.demo.app", + "type": "application" +} +`, + project: t.context.workspace._project + }); + + const workspace = { + byGlob: (actualPath) => { + t.is(actualPath, "/resources/sap/ui/demo/app/**/manifest.json", + "Reads all manifest.json files"); + return Promise.resolve([resource]); + }, + write: (actualResource) => { + t.fail("No resource should be rewritten"); + } + }; + + t.context.manifestEnhancerStub.returns([]); + + await enhanceManifest({ + workspace, + options: { + projectNamespace: "sap/ui/demo/app" + } + }); + + t.is(t.context.manifestEnhancerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.manifestEnhancerStub.calledWithExactly({ + resources: [resource], + fs: "fs interface" + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); + t.true(log.error.notCalled, "No errors should be logged"); +}); diff --git a/packages/builder/test/lib/tasks/escapeNonAsciiCharacters.js b/packages/builder/test/lib/tasks/escapeNonAsciiCharacters.js new file mode 100644 index 00000000000..4bcc2519a2d --- /dev/null +++ b/packages/builder/test/lib/tasks/escapeNonAsciiCharacters.js @@ -0,0 +1,135 @@ +import test from "ava"; +import escapeNonAsciiCharacters from "../../../lib/tasks/escapeNonAsciiCharacters.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +test("integration: escape non ascii characters (utf8, default)", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + + const content = `welcome=Willkommen {0}. Bitte geben Sie einen neuen Kontakt ein: +lastname=Nachname: +firstname=Vorname: +street=Straße:♥ +zip=PLZ: +city=Ort:`; + + const expected = `welcome=Willkommen {0}. Bitte geben Sie einen neuen Kontakt ein: +lastname=Nachname: +firstname=Vorname: +street=Stra\\u00dfe:\\u2665 +zip=PLZ: +city=Ort:`; + + const resource = createResource({ + path: "/i18n.properties", + string: content + }); + + await workspace.write(resource); + await escapeNonAsciiCharacters({ + workspace, + options: { + encoding: "UTF-8", + pattern: "/**/*.properties" + } + }); + + const escapedResource = await writer.byPath("/i18n.properties"); + + if (!escapedResource) { + t.fail("Could not find /i18n.properties in target"); + } else { + t.deepEqual(await escapedResource.getString(), expected); + } +}); + +test("integration: escape non ascii characters source encoding being (ISO-8859-1)", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + + // create buffer in ISO encoding + const content = Buffer.from(`welcome=Willkommen {0}. Bitte geben Sie einen neuen Kontakt ein: +lastname=Nachname: +firstname=Vorname: +street=Straße: +zip=PLZ: +city=Ort:`, "latin1"); + + const expected = `welcome=Willkommen {0}. Bitte geben Sie einen neuen Kontakt ein: +lastname=Nachname: +firstname=Vorname: +street=Stra\\u00dfe: +zip=PLZ: +city=Ort:`; + + const resource = createResource({ + path: "/i18n.properties", + buffer: content + }); + + await workspace.write(resource); + await escapeNonAsciiCharacters({ + workspace, + options: { + encoding: "ISO-8859-1", + pattern: "/**/*.properties" + } + }); + + const escapedResource = await writer.byPath("/i18n.properties"); + + if (!escapedResource) { + t.fail("Could not find /i18n.properties in target"); + } else { + t.deepEqual(await escapedResource.getString(), expected); + } +}); + +test("integration: escape non ascii characters source encoding being empty", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + + const error = await t.throwsAsync(escapeNonAsciiCharacters({ + workspace, + options: { + encoding: "", + pattern: "/**/*.properties" + } + })); + return t.is(error.message, "[escapeNonAsciiCharacters] Mandatory option 'encoding' not provided"); +}); + +test("integration: escape non ascii characters source encoding being UTF-16", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + + const error = await t.throwsAsync(escapeNonAsciiCharacters({ + workspace, + options: { + encoding: "utf16le", + pattern: "/**/*.properties" + } + })); + return t.is(error.message, `Encoding "utf16le" is not supported. Only UTF-8, ISO-8859-1 are allowed values`); +}); diff --git a/packages/builder/test/lib/tasks/generateCachebusterInfo.js b/packages/builder/test/lib/tasks/generateCachebusterInfo.js new file mode 100644 index 00000000000..b5c8f1c2712 --- /dev/null +++ b/packages/builder/test/lib/tasks/generateCachebusterInfo.js @@ -0,0 +1,137 @@ +import test from "ava"; +import path from "node:path"; +import {directoryDeepEqual, fileEqual, findFiles, readFileContent} from "../../utils/fshelper.js"; + +import {graphFromObject} from "@ui5/project/graph"; +import * as taskRepository from "../../../lib/tasks/taskRepository.js"; + +const __dirname = import.meta.dirname; + +const applicationGPath = path.join(__dirname, "..", "..", "fixtures", "application.g"); + +test("integration: Build application.g", async (t) => { + const destPath = path.join("test", "tmp", "build", "application.g", "cachebuster"); + const expectedPath = path.join("test", "expected", "build", "application.g", "cachebuster"); + const excludedTasks = ["escapeNonAsciiCharacters", "generateVersionInfo"]; + const includedTasks = ["generateCachebusterInfo"]; + + const cleanupCacheBusterInfo = (fileContent) => fileContent.replace(/(:\s+)(\d+)/g, ": 0"); + + const graph = await graphFromObject({ + dependencyTree: applicationGTree + }); + + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks, + includedTasks + }); + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + if (expectedFile.endsWith("sap-ui-cachebuster-info.json")) { + const destContent = JSON.parse(cleanupCacheBusterInfo(await readFileContent(destFile))); + const expectedContent = JSON.parse(cleanupCacheBusterInfo(await readFileContent(expectedFile))); + t.deepEqual(destContent, expectedContent); + } else { + await fileEqual(t, destFile, expectedFile); + } + })); + t.pass(); +}); + +test("integration: Build application.g with cachebuster using hashes", async (t) => { + const destPath = path.join("test", "tmp", "build", "application.g", "cachebuster_hash"); + const expectedPath = path.join("test", "expected", "build", "application.g", "cachebuster"); + const excludedTasks = ["escapeNonAsciiCharacters", "generateVersionInfo"]; + const includedTasks = ["generateCachebusterInfo"]; + + const cleanupCacheBusterInfo = (fileContent) => fileContent.replace(/(:\s+)("[^"]+")/g, ": \"\""); + + const graph = await graphFromObject({ + dependencyTree: applicationGTreeWithCachebusterHash + }); + + graph.setTaskRepository(taskRepository); + await graph.build({ + destPath, + excludedTasks, + includedTasks + }); + + const expectedFiles = await findFiles(expectedPath); + + // Check for all directories and files + await directoryDeepEqual(t, destPath, expectedPath); + + // Check for all file contents + await Promise.all(expectedFiles.map(async (expectedFile) => { + const relativeFile = path.relative(expectedPath, expectedFile); + const destFile = path.join(destPath, relativeFile); + if (expectedFile.endsWith("sap-ui-cachebuster-info.json")) { + const destContent = JSON.parse(cleanupCacheBusterInfo(await readFileContent(destFile))); + const expectedContent = JSON.parse(cleanupCacheBusterInfo(await readFileContent(destFile))); + t.deepEqual(destContent, expectedContent); + } else { + await fileEqual(t, destFile, expectedFile); + } + })); + t.pass(); +}); + +const applicationGTree = { + "id": "application.g", + "version": "1.0.0", + "path": applicationGPath, + "dependencies": [], + "configuration": { + "builder": {}, + "specVersion": "2.0", + "type": "application", + "metadata": { + "name": "application.g", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + } + } + } + } +}; + +const applicationGTreeWithCachebusterHash = { + "id": "application.g", + "version": "1.0.0", + "path": applicationGPath, + "dependencies": [], + "configuration": { + "builder": { + "cachebuster": { + "signatureType": "hash" + } + }, + "specVersion": "2.0", + "type": "application", + "metadata": { + "name": "application.g", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + } + } + } + } +}; diff --git a/packages/builder/test/lib/tasks/generateLibraryManifest.js b/packages/builder/test/lib/tasks/generateLibraryManifest.js new file mode 100644 index 00000000000..a4a83033427 --- /dev/null +++ b/packages/builder/test/lib/tasks/generateLibraryManifest.js @@ -0,0 +1,627 @@ +import test from "ava"; +import generateLibraryManifest from "../../../lib/tasks/generateLibraryManifest.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; + +function createWorkspace() { + return createAdapter({ + virBasePath: "/", + project: { + getName: () => "test.lib", + getVersion: () => "2.0.0", + } + }); +} + +async function assertCreatedManifest(t, oExpectedManifest) { + const {workspace, resources} = t.context; + + await Promise.all(resources.map((resource) => workspace.write(resource))); + + await generateLibraryManifest({ + workspace, + taskUtil: { + getProject: () => { + return { + getVersion: () => "1.0.0" + }; + } + }, + options: { + projectName: "Test Lib" + } + }); + + const resource = await workspace.byPath("/resources/test/lib/manifest.json"); + if (!resource) { + t.fail("Could not find /resources/test/lib/manifest.json in target"); + return; + } + + const buffer = await resource.getBuffer(); + t.deepEqual(JSON.parse(buffer), oExpectedManifest, "Correct content"); +} + +test("integration: Library without i18n bundle file", async (t) => { + t.context.workspace = createWorkspace(); + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + `, + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: false, + } + }, + }); +}); + +test("integration: Library with i18n bundle file (messagebundle.properties)", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/messagebundle.properties", + string: "KEY=VALUE", + project: t.context.workspace._project + })); + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + `, + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: { + bundleUrl: "messagebundle.properties", + supportedLocales: [""], + fallbackLocale: "" + } + } + }, + }); +}); + +test("integration: Library with i18n=true declared in .library", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + true + + + + + + + `, + project: t.context.workspace._project + })); + t.context.resources.push(createResource({ + path: "/resources/test/lib/messagebundle.properties", + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: { + bundleUrl: "messagebundle.properties", + supportedLocales: [""], + fallbackLocale: "" + } + } + }, + }); +}); + +test("integration: Library with i18n=true declared in .library and multiple locales", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + true + + + + + + + `, + project: t.context.workspace._project + })); + + t.context.resources.push(createResource({ + path: "/resources/test/lib/messagebundle.properties", + project: t.context.workspace._project + })); + + t.context.resources.push(createResource({ + path: "/resources/test/lib/messagebundle_en.properties", + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: { + bundleUrl: "messagebundle.properties", + supportedLocales: ["", "en"] + } + } + }, + }); +}); + +test("integration: Library with i18n=true declared in .library and single locale", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + true + + + + + + + `, + project: t.context.workspace._project + })); + + t.context.resources.push(createResource({ + path: "/resources/test/lib/messagebundle_de.properties", + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: { + bundleUrl: "messagebundle.properties", + supportedLocales: ["de"], + fallbackLocale: "de" + } + } + }, + }); +}); + +test("integration: Library with i18n=false declared in .library", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + false + + + + + + + `, + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: false + } + }, + }); +}); + +test("integration: Library with i18n=foo.properties declared in .library", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + foo.properties + + + + + + + `, + project: t.context.workspace._project + })); + + t.context.resources.push(createResource({ + path: "/resources/test/lib/foo.properties", + project: t.context.workspace._project + })); + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: { + bundleUrl: "foo.properties", + supportedLocales: [""], + fallbackLocale: "" + } + } + }, + }); +}); + +test("integration: Library with css=true declared in .library", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + true + + + + + + + `, + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: false + } + }, + }); +}); + +test("integration: Library with css=false declared in .library", async (t) => { + t.context.workspace = createWorkspace(); + + t.context.resources = []; + t.context.resources.push(createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + + + + false + + + + + + + `, + project: t.context.workspace._project + })); + + await assertCreatedManifest(t, { + "_version": "1.21.0", + "sap.app": { + applicationVersion: { + version: "2.0.0", + }, + description: "Test Lib", + embeds: [], + id: "test.lib", + offline: true, + resources: "resources.json", + title: "Test Lib", + type: "library", + }, + "sap.ui": { + supportedThemes: [], + technology: "UI5", + }, + "sap.ui5": { + dependencies: { + libs: {}, + minUI5Version: "1.0", + }, + library: { + i18n: false, + css: false + } + }, + }); +}); diff --git a/packages/builder/test/lib/tasks/generateResourcesJson.js b/packages/builder/test/lib/tasks/generateResourcesJson.js new file mode 100644 index 00000000000..a679d3bbadf --- /dev/null +++ b/packages/builder/test/lib/tasks/generateResourcesJson.js @@ -0,0 +1,187 @@ +import test from "ava"; +import sinon from "sinon"; +import esmock from "esmock"; +import {createAdapter} from "@ui5/fs/resourceFactory"; + +function createWorkspace() { + return createAdapter({ + virBasePath: "/", + project: { + metadata: { + name: "test.lib" + }, + version: "2.0.0", + dependencies: [ + { + metadata: { + name: "sap.ui.core" + }, + version: "1.0.0" + } + ] + } + }); +} + +function createDependencies() { + return { + byGlob: sinon.stub().resolves([]) + }; +} + +test.beforeEach(async (t) => { + t.context.resourceListCreatorStub = sinon.stub(); + t.context.resourceListCreatorStub.returns(Promise.resolve([])); + + t.context.generateResourcesJson = await esmock("../../../lib/tasks/generateResourcesJson.js", { + "../../../lib/processors/resourceListCreator": t.context.resourceListCreatorStub + }); +}); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("Missing 'dependencies' parameter", async (t) => { + const {generateResourcesJson} = t.context; + + await t.throwsAsync(generateResourcesJson({ + workspace: createWorkspace(), + options: { + projectName: "sap.ui.core" + } + }), { + // Not passing dependencies should result into a TypeError + name: "TypeError" + }); +}); + +test.serial("empty resources (sap.ui.core)", async (t) => { + const {generateResourcesJson, resourceListCreatorStub} = t.context; + + const result = await generateResourcesJson({ + workspace: createWorkspace(), + dependencies: createDependencies(), + options: { + projectName: "sap.ui.core" + } + }); + t.is(result, undefined, "no resources returned"); + t.is(resourceListCreatorStub.callCount, 1); + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].resources, [], "no resources are passed"); + const expectedOptions = { + externalResources: { + "sap/ui/core": [ + "*", + "sap/base/", + "sap/ui/" + ] + } + }; + t.deepEqual(resourceListCreatorStub.getCall(0).args[0].options, expectedOptions, "options match"); +}); + +test.serial("empty resources (my.lib)", async (t) => { + const {generateResourcesJson} = t.context; + + const result = await generateResourcesJson({ + workspace: createWorkspace(), + dependencies: createDependencies(), + options: { + projectName: "my.lib" + } + }); + t.is(result, undefined, "no resources returned"); + t.is(t.context.resourceListCreatorStub.callCount, 1); + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].resources, [], "no resources are passed"); + const expectedOptions = {}; + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].options, expectedOptions, "options match"); +}); + +test.serial("empty resources (my.lib with dependencies)", async (t) => { + const {generateResourcesJson} = t.context; + + const dependencyResources = [{"dependency": "resources"}]; + const dependencies = { + byGlob: sinon.stub().resolves(dependencyResources) + }; + + const result = await generateResourcesJson({ + workspace: createWorkspace(), + dependencies, + options: { + projectName: "my.lib" + } + }); + t.is(result, undefined, "no resources returned"); + t.is(t.context.resourceListCreatorStub.callCount, 1); + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].resources, [], "no resources are passed"); + const expectedOptions = {}; + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].options, expectedOptions, "options match"); + t.is(t.context.resourceListCreatorStub.getCall(0).args[0].dependencyResources, dependencyResources, + "dependencyResources reference should be passed to resourceListCreator"); +}); + +test.serial("Resources omitted from build result should be ignored", async (t) => { + const {generateResourcesJson} = t.context; + + const resource1 = {}; + const resource2 = {}; + const resource3 = {}; + + const workspace = createWorkspace(); + workspace.byGlob = sinon.stub().resolves([ + resource1, + resource2, + resource3, + ]); + + const dependencyResource1 = {}; + const dependencyResource2 = {}; + const dependencies = { + byGlob: sinon.stub().resolves([dependencyResource1, dependencyResource2]) + }; + + const taskUtil = { + getTag: sinon.stub(), + STANDARD_TAGS: { + OmitFromBuildResult: "TEST-OmitFromBuildResult-TEST" + } + }; + + // resources + taskUtil.getTag + .onCall(0).returns(false) + .onCall(1).returns(true) // second resource should be filtered out + .onCall(2).returns(false); + + // dependencyResources + taskUtil.getTag + .onCall(3).returns(true) // first dependencyResource should be filtered out + .onCall(4).returns(false); + + const result = await generateResourcesJson({ + workspace, + dependencies, + taskUtil, + options: { + projectName: "my.lib" + } + }); + + t.is(taskUtil.getTag.callCount, 5); + t.deepEqual(taskUtil.getTag.getCall(0).args, [resource1, "TEST-OmitFromBuildResult-TEST"]); + t.deepEqual(taskUtil.getTag.getCall(1).args, [resource2, "TEST-OmitFromBuildResult-TEST"]); + t.deepEqual(taskUtil.getTag.getCall(2).args, [resource3, "TEST-OmitFromBuildResult-TEST"]); + t.deepEqual(taskUtil.getTag.getCall(3).args, [dependencyResource1, "TEST-OmitFromBuildResult-TEST"]); + t.deepEqual(taskUtil.getTag.getCall(4).args, [dependencyResource2, "TEST-OmitFromBuildResult-TEST"]); + + t.is(result, undefined, "no resources returned"); + t.is(t.context.resourceListCreatorStub.callCount, 1); + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].resources, + [resource1, resource3], "only resources 1 and 3 are passed"); + const expectedOptions = {}; + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].options, expectedOptions, "options match"); + t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[0].dependencyResources, [dependencyResource2], + "dependencyResources reference should be passed to resourceListCreator"); +}); diff --git a/packages/builder/test/lib/tasks/generateThemeDesignerResources.js b/packages/builder/test/lib/tasks/generateThemeDesignerResources.js new file mode 100644 index 00000000000..5804b0db936 --- /dev/null +++ b/packages/builder/test/lib/tasks/generateThemeDesignerResources.js @@ -0,0 +1,1050 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.fsInterfaceStub = sinon.stub().returns({}); + + t.context.ReaderCollectionPrioritizedStub = sinon.stub().returns({ + byPath: sinon.stub() + }); + + t.context.ResourceStub = sinon.stub(); + t.context.libraryLessGeneratorStub = sinon.stub(); + + t.context.generateThemeDesignerResources = await esmock("../../../lib/tasks/generateThemeDesignerResources", { + "../../../lib/processors/libraryLessGenerator": t.context.libraryLessGeneratorStub, + "@ui5/fs/ReaderCollectionPrioritized": t.context.ReaderCollectionPrioritizedStub, + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "@ui5/fs/Resource": t.context.ResourceStub, + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("generateThemeDesignerResources: Library", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub, + ReaderCollectionPrioritizedStub} = t.context; + + const librarySourceLessResource1 = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/base/library.source.less") + }; + const librarySourceLessResource2 = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/library.source.less") + }; + const librarySourceLessResource3 = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/sap_fiori_3/library.source.less") + }; + + const clonedCoreBaseDotThemingResourceStub = { + setPath: sinon.stub() + }; + const coreBaseDotThemingResourceStub = { + clone: sinon.stub().resolves(clonedCoreBaseDotThemingResourceStub) + }; + ReaderCollectionPrioritizedStub.returns({ + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/themes/sap_fiori_3/.theming") { + return coreBaseDotThemingResourceStub; + } else { + return null; + } + }) + }); + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/sap/ui/demo/lib/themes/*/library.source.less") { + return [librarySourceLessResource1, librarySourceLessResource2, librarySourceLessResource3]; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource1 = {}; + const libraryLessResource2 = {}; + const libraryLessResource3 = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource1, libraryLessResource2, libraryLessResource3]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3", + projectNamespace: "sap/ui/demo/lib" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 1, "ReaderCollectionPrioritized should be created once"); + t.deepEqual(t.context.ReaderCollectionPrioritizedStub.getCall(0).args, [{ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: sap.ui.demo.lib`, + readers: [workspace, dependencies] + }]); + const combo = t.context.ReaderCollectionPrioritizedStub.getCall(0).returnValue; + + t.is(fsInterfaceStub.callCount, 1, "fsInterface should be created once"); + t.deepEqual(fsInterfaceStub.getCall(0).args, [combo], "fsInterface should be created for 'combo'"); + const fs = fsInterfaceStub.getCall(0).returnValue; + + t.is(libraryLessGeneratorStub.callCount, 1); + + t.deepEqual(libraryLessGeneratorStub.getCall(0).args[0], { + resources: [librarySourceLessResource1, librarySourceLessResource2, librarySourceLessResource3], + fs, + }, "libraryLessGenerator processor should be called with expected arguments"); + + t.is(ResourceStub.callCount, 3); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(0).args, [{ + path: "/resources/sap/ui/demo/lib/.theming", + string: JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/demo/lib", + sVersion: "1.2.3" + }, null, 2) + }]); + const libraryDotTheming = ResourceStub.getCall(0).returnValue; + + t.deepEqual(ResourceStub.getCall(1).args, [{ + path: "/resources/sap/ui/demo/lib/themes/base/.theming", + string: JSON.stringify({ + sEntity: "Theme", + sId: "base", + sVendor: "SAP" + }, null, 2) + }]); + const baseThemeDotTheming = ResourceStub.getCall(1).returnValue; + + t.deepEqual(ResourceStub.getCall(2).args, [{ + path: "/resources/sap/ui/demo/lib/themes/my_theme/.theming", + string: JSON.stringify({ + sEntity: "Theme", + sId: "my_theme", + sVendor: "SAP", + oExtends: "base" + }, null, 2) + }]); + const myThemeDotTheming = ResourceStub.getCall(2).returnValue; + + t.is(clonedCoreBaseDotThemingResourceStub.setPath.callCount, 1); + t.deepEqual(clonedCoreBaseDotThemingResourceStub.setPath.getCall(0).args, + ["/resources/sap/ui/demo/lib/themes/sap_fiori_3/.theming"]); + + t.is(workspace.write.callCount, 7); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for libraryDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], libraryDotTheming, + "workspace.write should be called with libraryDotTheming"); + t.is(workspace.write.getCall(1).args.length, 1, + "workspace.write for baseThemeDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(1).args[0], baseThemeDotTheming, + "workspace.write should be called with baseThemeDotTheming"); + t.is(workspace.write.getCall(2).args.length, 1, + "workspace.write for myThemeDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(2).args[0], myThemeDotTheming, + "workspace.write should be called with myThemeDotTheming"); + t.is(workspace.write.getCall(3).args.length, 1, + "workspace.write for clonedCoreBaseDotThemingResourceStub should be called with 1 argument"); + t.is(workspace.write.getCall(3).args[0], clonedCoreBaseDotThemingResourceStub, + "workspace.write should be called with clonedCoreBaseDotThemingResourceStub"); + t.is(workspace.write.getCall(4).args.length, 1, + "workspace.write for libraryLessResource1 should be called with 1 argument"); + t.is(workspace.write.getCall(4).args[0], libraryLessResource1, + "workspace.write should be called with libraryLessResource1"); + t.is(workspace.write.getCall(5).args.length, 1, + "workspace.write for libraryLessResource2 should be called with 1 argument"); + t.is(workspace.write.getCall(5).args[0], libraryLessResource2, + "workspace.write should be called with libraryLessResource2"); +}); + +test.serial("generateThemeDesignerResources: Library sap.ui.core", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/base/library.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/sap/ui/core/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/themes/base/.theming") { + return {}; + } else { + return null; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + version: "1.2.3", + projectNamespace: "sap/ui/core" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 1, "ReaderCollectionPrioritized should be created once"); + t.deepEqual(t.context.ReaderCollectionPrioritizedStub.getCall(0).args, [{ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: sap.ui.core`, + readers: [workspace, dependencies] + }]); + const combo = t.context.ReaderCollectionPrioritizedStub.getCall(0).returnValue; + + t.is(fsInterfaceStub.callCount, 1, "fsInterface should be created once"); + t.deepEqual(fsInterfaceStub.getCall(0).args, [combo], "fsInterface should be created for 'combo'"); + const fs = fsInterfaceStub.getCall(0).returnValue; + + t.is(libraryLessGeneratorStub.callCount, 1); + + t.deepEqual(libraryLessGeneratorStub.getCall(0).args[0], { + resources: [librarySourceLessResource], + fs, + }, "libraryLessGenerator processor should be called with expected arguments"); + + t.is(ResourceStub.callCount, 1); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(0).args, [{ + path: "/resources/sap/ui/core/.theming", + string: JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "1.2.3", + aFiles: [ + "library", + "global", + "css_variables" + ] + }, null, 2) + }]); + const libraryDotTheming = ResourceStub.getCall(0).returnValue; + + t.is(workspace.write.callCount, 2); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for libraryDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], libraryDotTheming, + "workspace.write should be called with libraryDotTheming"); + t.is(workspace.write.getCall(1).args.length, 1, + "workspace.write for libraryLessResource should be called with 1 argument"); + t.is(workspace.write.getCall(1).args[0], libraryLessResource, + "workspace.write should be called with libraryLessResource"); +}); + +test.serial("generateThemeDesignerResources: Library sap.ui.core with existing library .theming", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/base/library.source.less") + }; + + const coreLibraryDotThemingResource = { + getString: async () => JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ] + }, null, 2), + setString: sinon.stub() + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/sap/ui/core/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/themes/base/.theming") { + return {}; + } else if (virPath === "/resources/sap/ui/core/.theming") { + return coreLibraryDotThemingResource; + } else { + return null; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + version: "1.2.3", + projectNamespace: "sap/ui/core" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 1, "ReaderCollectionPrioritized should be created once"); + t.deepEqual(t.context.ReaderCollectionPrioritizedStub.getCall(0).args, [{ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: sap.ui.core`, + readers: [workspace, dependencies] + }]); + const combo = t.context.ReaderCollectionPrioritizedStub.getCall(0).returnValue; + + t.is(fsInterfaceStub.callCount, 1, "fsInterface should be created once"); + t.deepEqual(fsInterfaceStub.getCall(0).args, [combo], "fsInterface should be created for 'combo'"); + const fs = fsInterfaceStub.getCall(0).returnValue; + + t.is(libraryLessGeneratorStub.callCount, 1); + + t.deepEqual(libraryLessGeneratorStub.getCall(0).args[0], { + resources: [librarySourceLessResource], + fs, + }, "libraryLessGenerator processor should be called with expected arguments"); + + t.is(ResourceStub.callCount, 0, "No new resource should be created"); + + t.is(coreLibraryDotThemingResource.setString.callCount, 1); + t.deepEqual(coreLibraryDotThemingResource.setString.getCall(0).args, [ + JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ], + sVersion: "1.2.3", + }, null, 2) + ]); + + t.is(workspace.write.callCount, 2); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for coreLibraryDotThemingResource should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], coreLibraryDotThemingResource, + "workspace.write should be called with libraryDotTheming"); + t.is(workspace.write.getCall(1).args.length, 1, + "workspace.write for libraryLessResource should be called with 1 argument"); + t.is(workspace.write.getCall(1).args[0], libraryLessResource, + "workspace.write should be called with libraryLessResource"); +}); + +test.serial("generateThemeDesignerResources: Library sap.ui.core without themes, " + +"with existing library .theming with version", async (t) => { + // NOTE: This tests the case when sap.ui.core has no themes, which is not a likely scenario. + // But as the underlying functionality might be used in other scenarios in future, it is tested here. + + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const coreLibraryDotThemingResource = { + getString: async () => JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "0.0.0", // existing version should be ignored + aFiles: [ + "existing", "entries" + ] + }, null, 2), + setString: sinon.stub() + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + return []; + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/.theming") { + return coreLibraryDotThemingResource; + } else { + return null; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + version: "1.2.3", + projectNamespace: "sap/ui/core" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 0, "ReaderCollectionPrioritized should not be created"); + + t.is(fsInterfaceStub.callCount, 0, "fsInterface should not be created"); + + t.is(libraryLessGeneratorStub.callCount, 0); + + t.is(ResourceStub.callCount, 0, "No new resource should be created"); + + t.is(coreLibraryDotThemingResource.setString.callCount, 1); + t.deepEqual(coreLibraryDotThemingResource.setString.getCall(0).args, [ + JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "1.2.3", + aFiles: [ + "existing", "entries" + ], + bIgnore: true + }, null, 2) + ]); + + t.is(workspace.write.callCount, 1); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for coreLibraryDotThemingResource should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], coreLibraryDotThemingResource, + "workspace.write should be called with libraryDotTheming"); +}); + +test.serial("generateThemeDesignerResources: Library sap.ui.core with existing invalid library .theming", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const coreLibraryDotThemingResource = { + getPath: () => "/resources/sap/ui/core/.theming", + getString: async () => JSON.stringify({ + sEntity: "Library", + sId: "sap/m" + }, null, 2), + setString: sinon.stub() + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + return []; + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/.theming") { + return coreLibraryDotThemingResource; + } else { + return null; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await t.throwsAsync(generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + version: "1.2.3", + projectNamespace: "sap/ui/core" + } + }), { + message: "Incorrect 'sId' value 'sap/m' in /resources/sap/ui/core/.theming: Expected 'sap/ui/core'" + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 0, "ReaderCollectionPrioritized should not be created"); + + t.is(fsInterfaceStub.callCount, 0, "fsInterface should not be created"); + + t.is(libraryLessGeneratorStub.callCount, 0); + + t.is(ResourceStub.callCount, 0, "No new resource should be created"); + + t.is(coreLibraryDotThemingResource.setString.callCount, 0); + + t.is(workspace.write.callCount, 0); +}); + +test.serial("generateThemeDesignerResources: Library sap.ui.documentation is skipped", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const workspace = { + byGlob: sinon.stub(), + write: sinon.stub() + }; + + await generateThemeDesignerResources({ + workspace: {}, + dependencies: {}, + options: { + projectName: "sap.ui.documentation", + version: "1.2.3", + projectNamespace: "sap/ui/documentation" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 0); + t.is(fsInterfaceStub.callCount, 0); + t.is(libraryLessGeneratorStub.callCount, 0); + t.is(ResourceStub.callCount, 0); + t.is(workspace.byGlob.callCount, 0); + t.is(workspace.write.callCount, 0); +}); + +test.serial("generateThemeDesignerResources: Library without themes", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const workspace = { + byGlob: sinon.stub().callsFake(async () => { + return []; + }), + write: sinon.stub() + }; + + await generateThemeDesignerResources({ + workspace, + dependencies: {}, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3", + projectNamespace: "sap/ui/demo/lib" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 0); + t.is(fsInterfaceStub.callCount, 0); + t.is(libraryLessGeneratorStub.callCount, 0); + + t.is(ResourceStub.callCount, 1); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(0).args, [{ + path: "/resources/sap/ui/demo/lib/.theming", + string: JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/demo/lib", + sVersion: "1.2.3", + bIgnore: true + }, null, 2) + }]); + const libraryDotTheming = ResourceStub.getCall(0).returnValue; + + t.is(workspace.write.callCount, 1); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for libraryDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], libraryDotTheming, + "workspace.write should be called with libraryDotTheming"); +}); + +test.serial("generateThemeDesignerResources: Theme-Library", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, fsInterfaceStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/library.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/**/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 1, "ReaderCollectionPrioritized should be created once"); + t.deepEqual(t.context.ReaderCollectionPrioritizedStub.getCall(0).args, [{ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: sap.ui.demo.lib`, + readers: [workspace, dependencies] + }]); + const combo = t.context.ReaderCollectionPrioritizedStub.getCall(0).returnValue; + + t.is(fsInterfaceStub.callCount, 1, "fsInterface should be created once"); + t.deepEqual(fsInterfaceStub.getCall(0).args, [combo], "fsInterface should be created for 'combo'"); + const fs = fsInterfaceStub.getCall(0).returnValue; + + t.is(libraryLessGeneratorStub.callCount, 1); + + t.deepEqual(libraryLessGeneratorStub.getCall(0).args[0], { + resources: [librarySourceLessResource], + fs, + }, "libraryLessGenerator processor should be called with expected arguments"); + + t.is(ResourceStub.callCount, 1); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(0).args, [{ + path: "/resources/sap/ui/demo/lib/themes/my_theme/.theming", + string: JSON.stringify({ + sEntity: "Theme", + sId: "my_theme", + sVendor: "SAP", + oExtends: "base" + }, null, 2) + }]); + const myThemeDotTheming = ResourceStub.getCall(0).returnValue; + + t.is(workspace.write.callCount, 2); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for myThemeDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], myThemeDotTheming, + "workspace.write should be called with myThemeDotTheming"); + t.is(workspace.write.getCall(1).args.length, 1, + "workspace.write for libraryLessResource should be called with 1 argument"); + t.is(workspace.write.getCall(1).args[0], libraryLessResource, + "workspace.write should be called with libraryLessResource"); +}); + +test.serial("generateThemeDesignerResources: Theme-Library with CSS Variables", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/library.source.less") + }; + + const cssVariablesSourceResource = { + getString: sinon.stub().returns("My Content"), + }; + + const cssVariableSourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/**/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else if (globPattern === "/resources/**/themes/*/css_variables.source.less") { + return [cssVariableSourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") { + return cssVariablesSourceResource; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3" + } + }); + + t.is(ResourceStub.callCount, 2); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(1).args, [{ + path: "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.less", + string: +`/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../base/css_variables.less"; + +/* START "css_variables.source.less" */ +My Content +/* END "css_variables.source.less" */ + +@import "../../../../../../sap/ui/core/themes/my_theme/global.less"; +` + }]); + const cssVariableResource = ResourceStub.getCall(1).returnValue; + + t.is(workspace.write.callCount, 3); + t.is(workspace.write.getCall(2).args.length, 1, + "workspace.write for cssVariableResource should be called with 1 argument"); + t.is(workspace.write.getCall(2).args[0], cssVariableResource, + "workspace.write should be called with cssVariableResource"); +}); + +test.serial("generateThemeDesignerResources: Theme-Library with CSS Variables with namespace", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/library.source.less") + }; + + const cssVariablesSourceResource = { + getString: sinon.stub().returns("My Content from Namespace"), + }; + + const cssVariableSourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/sap/ui/demo/lib/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else if (globPattern === "/resources/sap/ui/demo/lib/themes/*/css_variables.source.less") { + return [cssVariableSourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") { + return cssVariablesSourceResource; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3", + projectNamespace: "sap/ui/demo/lib" + } + }); + + t.is(t.context.ReaderCollectionPrioritizedStub.callCount, 1, "ReaderCollectionPrioritized should be created once"); + t.deepEqual(t.context.ReaderCollectionPrioritizedStub.getCall(0).args, [{ + name: `generateThemeDesignerResources - prioritize workspace over dependencies: sap.ui.demo.lib`, + readers: [workspace, dependencies] + }]); + + t.is(ResourceStub.callCount, 3); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(2).args, [{ + path: "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.less", + string: +`/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../base/css_variables.less"; + +/* START "css_variables.source.less" */ +My Content from Namespace +/* END "css_variables.source.less" */ + +@import "../../../../../../sap/ui/core/themes/my_theme/global.less"; +` + }]); + const cssVariableResource = ResourceStub.getCall(2).returnValue; + + t.is(workspace.write.callCount, 4); + t.is(workspace.write.getCall(3).args.length, 1, + "workspace.write for cssVariableResource should be called with 1 argument"); + t.is(workspace.write.getCall(3).args[0], cssVariableResource, + "workspace.write should be called with cssVariableResource"); +}); + +test.serial("generateThemeDesignerResources: Theme-Library with CSS Variables with base theme", async (t) => { + const { + sinon, + generateThemeDesignerResources, + libraryLessGeneratorStub, + ResourceStub, + ReaderCollectionPrioritizedStub + } = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/library.source.less") + }; + + const cssVariablesSourceResource = { + getString: sinon.stub().returns("My Content with Base Theme"), + }; + + const cssVariableSourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") + }; + + const baseLessResource = {}; + + ReaderCollectionPrioritizedStub.returns({ + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/core/themes/my_theme/base.less") { + return baseLessResource; + } else { + return null; + } + }) + }); + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/**/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else if (globPattern === "/resources/**/themes/*/css_variables.source.less") { + return [cssVariableSourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") { + return cssVariablesSourceResource; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3" + } + }); + + t.is(ResourceStub.callCount, 2); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(1).args, [{ + path: "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.less", + string: +`/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../base/css_variables.less"; + +/* START "css_variables.source.less" */ +My Content with Base Theme +/* END "css_variables.source.less" */ + +@import "../../../../../../../Base/baseLib/my_theme/base.less"; +@import "../../../../../../sap/ui/core/themes/my_theme/global.less"; +` + }]); + const cssVariableResource = ResourceStub.getCall(1).returnValue; + + t.is(workspace.write.callCount, 3); + t.is(workspace.write.getCall(2).args.length, 1, + "workspace.write for cssVariableResource should be called with 1 argument"); + t.is(workspace.write.getCall(2).args[0], cssVariableResource, + "workspace.write should be called with cssVariableResource"); +}); + +test.serial("generateThemeDesignerResources: Base Theme-Library with CSS Variables", async (t) => { + const { + sinon, + generateThemeDesignerResources, + libraryLessGeneratorStub, + ResourceStub + } = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/base/library.source.less") + }; + + const cssVariablesSourceResource = { + getString: sinon.stub().returns("My Base Theme Content"), + }; + + const cssVariableSourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/demo/lib/themes/base/css_variables.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/**/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else if (globPattern === "/resources/**/themes/*/css_variables.source.less") { + return [cssVariableSourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + if (virPath === "/resources/sap/ui/demo/lib/themes/my_theme/css_variables.source.less") { + return cssVariablesSourceResource; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3" + } + }); + + t.is(ResourceStub.callCount, 2); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(1).args, [{ + path: "/resources/sap/ui/demo/lib/themes/base/css_variables.less", + string: +`/* NOTE: This file was generated as an optimized version of "css_variables.source.less" for the Theme Designer. */ + +@import "../../../../../../sap/ui/core/themes/base/global.less"; +` + }]); + const cssVariableResource = ResourceStub.getCall(1).returnValue; + + t.is(workspace.write.callCount, 3); + t.is(workspace.write.getCall(2).args.length, 1, + "workspace.write for cssVariableResource should be called with 1 argument"); + t.is(workspace.write.getCall(2).args[0], cssVariableResource, + "workspace.write should be called with cssVariableResource"); +}); + +test.serial("generateThemeDesignerResources: .theming file missing in sap.ui.core library source`", async (t) => { + const {sinon, generateThemeDesignerResources, libraryLessGeneratorStub, ResourceStub} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/sap/ui/core/themes/base/library.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/sap/ui/core/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else { + return []; + } + }), + byPath: sinon.stub().callsFake(async (virPath) => { + return null; + }), + write: sinon.stub() + }; + const dependencies = {}; + + const libraryLessResource = {}; + + libraryLessGeneratorStub.resolves([libraryLessResource]); + + await t.throwsAsync(generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.core", + version: "1.2.3", + projectNamespace: "sap/ui/core" + } + }), { + message: ".theming file for theme base missing in sap.ui.core library source" + }); + + t.is(ResourceStub.callCount, 1); + t.true(ResourceStub.alwaysCalledWithNew()); + + t.deepEqual(ResourceStub.getCall(0).args, [{ + path: "/resources/sap/ui/core/.theming", + string: JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "1.2.3", + aFiles: [ + "library", + "global", + "css_variables" + ] + }, null, 2) + }]); + const libraryDotTheming = ResourceStub.getCall(0).returnValue; + + t.is(workspace.write.callCount, 1); + t.is(workspace.write.getCall(0).args.length, 1, + "workspace.write for libraryDotTheming should be called with 1 argument"); + t.is(workspace.write.getCall(0).args[0], libraryDotTheming, + "workspace.write should be called with libraryDotTheming"); +}); + +test.serial("generateThemeDesignerResources: Failed to extract library name from theme folder path", async (t) => { + const {sinon, generateThemeDesignerResources} = t.context; + + const librarySourceLessResource = { + getPath: sinon.stub().returns("/resources/foo/library.source.less") + }; + + const workspace = { + byGlob: sinon.stub().callsFake(async (globPattern) => { + if (globPattern === "/resources/**/themes/*/library.source.less") { + return [librarySourceLessResource]; + } else { + return []; + } + }), + write: sinon.stub() + }; + const dependencies = {}; + + await t.throwsAsync(generateThemeDesignerResources({ + workspace, + dependencies, + options: { + projectName: "sap.ui.demo.lib", + version: "1.2.3" + } + }), { + message: "Failed to extract library name from theme folder path: /resources/foo" + }); + + t.is(workspace.write.callCount, 0); +}); diff --git a/packages/builder/test/lib/tasks/generateVersionInfo.js b/packages/builder/test/lib/tasks/generateVersionInfo.js new file mode 100644 index 00000000000..edd608328e6 --- /dev/null +++ b/packages/builder/test/lib/tasks/generateVersionInfo.js @@ -0,0 +1,1184 @@ +import test from "ava"; +import path from "node:path"; +import * as resourceFactory from "@ui5/fs/resourceFactory"; +import sinon from "sinon"; +import esmock from "esmock"; + +const __dirname = import.meta.dirname; + +const projectCache = {}; + +/** + * + * @param {string[]} names e.g. ["lib", "a"] + * @param {string} [version="3.0.0-"] Project version + * @param {string} [type="library"] Project type + * @returns {object} Project mock + */ +const createProjectMetadata = (names, version, type) => { + const key = names.join("."); + + // Cache projects in order to return same object instance + // AbstractAdapter will compare the project instances of the adapter + // to the resource and denies a write if they don't match + if (projectCache[key]) { + return projectCache[key]; + } + return projectCache[key] = { + getName: () => key, + getNamespace: () => names.join("/"), + getVersion: () => version || "3.0.0-" + key, + getType: () => type || "library" + }; +}; + +function createWorkspace() { + return resourceFactory.createAdapter({ + virBasePath: "/", + project: createProjectMetadata(["test", "lib"], "2.0.0") + }); +} + +function createDependencies(oOptions = { + virBasePath: "/resources/", + fsBasePath: path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo", "main", "src") +}) { + oOptions = Object.assign({ + project: createProjectMetadata(["test", "lib3"], "3.0.0") + }, oOptions); + return resourceFactory.createAdapter(oOptions); +} + +async function createOptions(t, options) { + const {workspace, dependencies, resources} = t.context; + + await Promise.all(resources.map((resource) => workspace.write(resource))); + const oOptions = { + workspace, + dependencies + }; + oOptions.options = options || { + projectName: "Test Lib", + pattern: "/**/*.js", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }; + return oOptions; +} + +async function assertCreatedVersionInfo(t, oExpectedVersionInfo, oOptions) { + const {generateVersionInfo} = t.context; + + await generateVersionInfo(oOptions); + + const resource = await oOptions.workspace.byPath("/resources/sap-ui-version.json"); + if (!resource) { + t.fail("Could not find /resources/sap-ui-version.json in target"); + return; + } + + const buffer = await resource.getBuffer(); + const currentVersionInfo = JSON.parse(buffer); + + t.is(currentVersionInfo.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + + delete currentVersionInfo.buildTimestamp; // removing to allow deep comparison + currentVersionInfo.libraries.forEach((lib) => { + t.is(lib.buildTimestamp.length, 12, "Timestamp should have length of 12 (yyyyMMddHHmm)"); + delete lib.buildTimestamp; // removing to allow deep comparison + }); + + currentVersionInfo.libraries.sort((libraryA, libraryB) => { + return libraryA.name.localeCompare(libraryB.name); + }); + + t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content"); +} + +test.beforeEach(async (t) => { + t.context.verboseLogStub = sinon.stub(); + t.context.errorLogStub = sinon.stub(); + t.context.warnLogStub = sinon.stub(); + t.context.infoLogStub = sinon.stub(); + t.context.sillyLogStub = sinon.stub(); + t.context.log = { + verbose: t.context.verboseLogStub, + error: t.context.errorLogStub, + warn: t.context.warnLogStub, + info: t.context.infoLogStub, + silly: t.context.sillyLogStub, + isLevelEnabled: () => true + }; + + t.context.generateVersionInfo = await esmock("../../../lib/tasks/generateVersionInfo.js", { + + }, { + "@ui5/logger": { + getLogger: sinon.stub().withArgs("builder:processors:versionInfoGenerator").returns(t.context.log) + } + }); +}); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("integration: Library without i18n bundle file", async (t) => { + t.context.workspace = createWorkspace(); + t.context.dependencies = createDependencies(); + + t.context.resources = []; + t.context.resources.push(resourceFactory.createResource({ + path: "/resources/test/lib/.library", + string: ` + + + + test.lib + SAP SE + + 2.0.0 + + Test Lib + + + `, + project: t.context.workspace._project + })); + + const oOptions = await createOptions(t); + await assertCreatedVersionInfo(t, { + "libraries": [{ + "name": "test.lib3", + "scmRevision": "", + "version": "3.0.0" + }], + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + }, oOptions); + + t.is(t.context.verboseLogStub.callCount, 1); + t.is(t.context.verboseLogStub.getCall(0).args[0], + "Cannot add meta information for library 'test.lib3'. The manifest.json file cannot be found"); +}); + +/** + * + * @param {@ui5/fs/DuplexCollection} dependencies + * @param {@ui5/fs/resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @param {object[]} deps + * @param {string[]} [embeds] + * @param {string} [embeddedBy] + * @returns {Promise} + */ +const createManifestResource = async (dependencies, resourceFactory, names, deps, embeds, embeddedBy) => { + const content = { + "sap.app": { + "id": names.join("."), + "embeds": [] + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.84", + "libs": {} + } + } + }; + + const libs = {}; + deps.forEach((dep) => { + libs[dep.name] = { + "minVersion": "1.84.0" + }; + if (dep.lazy) { + libs[dep.name].lazy = true; + } + }); + content["sap.ui5"]["dependencies"]["libs"] = libs; + if (embeds !== undefined) { + content["sap.app"]["embeds"] = embeds; + } + if (embeddedBy !== undefined) { + content["sap.app"]["embeddedBy"] = embeddedBy; + } + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/manifest.json`, + string: JSON.stringify(content, null, 2) + })); +}; + +/** + * @param {@ui5/fs/DuplexCollection} dependencies + * @param {@ui5/fs/resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @returns {Promise} + */ +async function createDotLibrary(dependencies, resourceFactory, names) { + await dependencies.write(resourceFactory.createResource({ + path: `/resources/${names.join("/")}/.library`, + string: ` + + + ${names.join(".")} + SAP SE + + 2.0.0 + + Library ${names.slice(1).join(".").toUpperCase()} + + ` + })); +} + +/** + * + * @param {@ui5/fs/DuplexCollection} dependencies + * @param {@ui5/fs/resourceFactory} resourceFactory + * @param {string[]} names e.g. ["lib", "a"] + * @param {object[]} deps + * @param {string[]} [embeds] + */ +const createResources = async (dependencies, resourceFactory, names, deps, embeds) => { + await createDotLibrary(dependencies, resourceFactory, names); + await createManifestResource(dependencies, resourceFactory, names, deps, embeds); +}; + +function createDepWorkspace(names, oOptions = { + virBasePath: "/resources" +}) { + oOptions = Object.assign({ + project: createProjectMetadata(names) + }, oOptions); + return resourceFactory.createAdapter(oOptions); +} + +test.serial("integration: sibling eager to lazy", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + + // lib.a + await createResources(dependenciesA, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "version": "3.0.0-lib.b", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "version": "3.0.0-lib.c", + }], + }, oOptions); +}); + +test.serial("integration: sibling lazy to eager", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c + // lib.b => lib.c + // lib.c => + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + + // lib.a + await createResources(dependenciesA, resourceFactory, ["lib", "a"], + [{name: "lib.b"}, {name: "lib.c", lazy: true}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], [{name: "lib.c"}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {} + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "version": "3.0.0-lib.b", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "version": "3.0.0-lib.c", + }], + }, oOptions); +}); + +test.serial("integration: children eager to lazy", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b + // lib.b => lib.c (true) + // lib.c => + + // expected outcome + // lib.a => lib.b, lib.c (true) + // lib.b => lib.c (true) + // lib.c => + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + + // lib.a + await createResources(dependenciesA, resourceFactory, ["lib", "a"], + [{name: "lib.b"}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], + [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "version": "3.0.0-lib.b", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "version": "3.0.0-lib.c", + }], + }, oOptions); +}); + +test.serial("integration: children lazy to eager", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b (true) + // lib.b => lib.c + // lib.c => + + // expected outcome + // lib.a => lib.b (true), lib.c (true) + // lib.b => lib.c + // lib.c => + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + + // lib.a + await createResources(dependenciesA, resourceFactory, ["lib", "a"], + [{name: "lib.b", lazy: true}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], + [{name: "lib.c"}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": { + "lazy": true + }, + "lib.c": { + "lazy": true + } + } + } + }, + }, + { + "name": "lib.b", + "scmRevision": "", + "version": "3.0.0-lib.b", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {} + } + } + }, + }, + { + "name": "lib.c", + "scmRevision": "", + "version": "3.0.0-lib.c", + }], + }, oOptions); +}); + +test.serial("integration: Library with dependencies and subcomponent complex scenario", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => lib.d, lib.e (true) + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c + + // expected outcome + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) + // lib.c => lib.d, lib.e + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + const dependenciesD = createDepWorkspace(["lib", "d"], {virBasePath: "/"}); + const dependenciesE = createDepWorkspace(["lib", "e"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependenciesA, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependenciesA, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e", lazy: true}]); + + // lib.d + await createResources(dependenciesD, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + + // lib.e + await createResources(dependenciesE, resourceFactory, ["lib", "e"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC, dependenciesD, dependenciesE] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.a", + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, + "version": "3.0.0-lib.b", + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.c", + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.d", + }, + { + "name": "lib.e", + "scmRevision": "", + "version": "3.0.0-lib.e", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + } + } + }, + }, oOptions); +}); + +test.serial("integration: Library with dependencies and subcomponent bigger scenario", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // input + // lib.a => lib.b, lib.c + // lib.b => lib.c (true) + // lib.c => lib.d, lib.e (true) + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c + + // expected outcome + // lib.a => lib.b, lib.c, lib.d, lib.e + // lib.b => lib.c (true), lib.d (true), lib.e (true) + // lib.c => lib.d, lib.e + // lib.d => lib.e + // lib.e => + // lib.a.sub.fold => lib.c, lib.d, lib.e + + // dependencies + const dependenciesA = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + const dependenciesB = createDepWorkspace(["lib", "b"], {virBasePath: "/"}); + const dependenciesC = createDepWorkspace(["lib", "c"], {virBasePath: "/"}); + const dependenciesD = createDepWorkspace(["lib", "d"], {virBasePath: "/"}); + const dependenciesE = createDepWorkspace(["lib", "e"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependenciesA, resourceFactory, ["lib", "a"], [{name: "lib.b"}, {name: "lib.c"}], embeds); + // sub + await createManifestResource(dependenciesA, resourceFactory, ["lib", "a", "sub", "fold"], [{name: "lib.c"}]); + + // lib.b + await createResources(dependenciesB, resourceFactory, ["lib", "b"], [{name: "lib.c", lazy: true}]); + + // lib.c + await createResources(dependenciesC, resourceFactory, ["lib", "c"], [{name: "lib.d"}, {name: "lib.e", lazy: true}]); + + // lib.d + await createResources(dependenciesD, resourceFactory, ["lib", "d"], [{name: "lib.e"}]); + + // lib.e + await createResources(dependenciesE, resourceFactory, ["lib", "e"], []); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies: resourceFactory.createReaderCollection({ + name: "dependencies", + readers: [dependenciesA, dependenciesB, dependenciesC, dependenciesD, dependenciesE] + }) + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.b": {}, + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.a", + }, + { + "name": "lib.b", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": { + "lazy": true + }, + "lib.d": { + "lazy": true + }, + "lib.e": { + "lazy": true + } + } + } + }, + "version": "3.0.0-lib.b", + }, + { + "name": "lib.c", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.d": {}, + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.c", + }, + { + "name": "lib.d", + "scmRevision": "", + "manifestHints": { + "dependencies": { + "libs": { + "lib.e": {} + } + } + }, + "version": "3.0.0-lib.d", + }, + { + "name": "lib.e", + "scmRevision": "", + "version": "3.0.0-lib.e", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "lib.c": {}, + "lib.d": {}, + "lib.e": {} + } + } + } + } + }, + }, oOptions); +}); + +test.serial("integration: Library without dependencies and embeds and embeddedBy", async (t) => { + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, "../../"); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + }], + "components": { + "lib.a.sub.fold": { + "library": "lib.a" + } + }, + }, oOptions); +}); + +test.serial("integration: Library without dependencies and embeddedBy undefined", async (t) => { + const {verboseLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, undefined); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a" + } + }, + }, oOptions); + + t.is(verboseLogStub.callCount, 1); + t.is(verboseLogStub.firstCall.args[0], + " Component doesn't declare 'sap.app/embeddedBy', don't list it as 'embedded'"); +}); + +test.serial("integration: Library without dependencies and embeddedBy not a string", async (t) => { + const {errorLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, {}); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a" + } + }, + }, oOptions); + + t.is(errorLogStub.callCount, 1); + t.is(errorLogStub.firstCall.args[0], + " Component '/resources/lib/a/sub/fold': property 'sap.app/embeddedBy' is of type 'object' " + + "(expected 'string'), it won't be listed as 'embedded'"); +}); + +test.serial("integration: Library without dependencies and embeddedBy empty string", async (t) => { + const {errorLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, ""); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a" + } + }, + }, oOptions); + + t.is(errorLogStub.callCount, 1); + t.is(errorLogStub.firstCall.args[0], + " Component '/resources/lib/a/sub/fold': property 'sap.app/embeddedBy' has an empty string value " + + "(which is invalid), it won't be listed as 'embedded'"); +}); + +test.serial("integration: Library without dependencies and embeddedBy path not correct", async (t) => { + const {verboseLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + const embeds = ["sub/fold"]; + await createResources(dependencies, resourceFactory, ["lib", "a"], [], embeds); + // sub + await createManifestResource(dependencies, resourceFactory, ["lib", "a", "sub", "fold"], [], + undefined, "../"); + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a" + }], + "components": { + "lib.a.sub.fold": { + "hasOwnPreload": true, + "library": "lib.a" + } + }, + }, oOptions); + + t.is(verboseLogStub.callCount, 1); + t.is(verboseLogStub.firstCall.args[0], + " Component's 'sap.app/embeddedBy' points to '/resources/lib/a/sub/', don't list it as 'embedded'"); +}); + +test.serial("integration: Library with manifest with invalid dependency", async (t) => { + const {infoLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["lib", "a"], {virBasePath: "/"}); + + // lib.a + await createResources(dependencies, resourceFactory, ["lib", "a"], [{name: "non.existing"}]); + + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [{ + "name": "lib.a", + "scmRevision": "", + "version": "3.0.0-lib.a", + "manifestHints": { + "dependencies": { + "libs": { + "non.existing": {}, + }, + }, + } + }], + }, oOptions); + + t.is(infoLogStub.callCount, 1); + t.is(infoLogStub.firstCall.args[0], + "Cannot find dependency 'non.existing' defined in the manifest.json or .library file of project 'lib.a'. " + + "This might prevent some UI5 runtime performance optimizations from taking effect. " + + "Please double check your project's dependency configuration."); +}); + +test.serial("integration: Library of type 'module'", async (t) => { + const {infoLogStub} = t.context; + const workspace = createWorkspace(); + + await createDotLibrary(workspace, resourceFactory, ["test", "lib"]); + + // dependencies + const dependencies = createDepWorkspace(["module", "x"], { + virBasePath: "/", + project: createProjectMetadata(["module", "x"], "1.0.0", "module") + }); + + // lib.a + await createResources(dependencies, resourceFactory, ["module", "x"], [{name: "non.existing"}]); + + + const oOptions = { + options: { + projectName: "Test Lib", + pattern: "/resources/**/.library", + rootProject: createProjectMetadata(["myname"], "1.33.7") + }, + workspace, + dependencies + }; + await assertCreatedVersionInfo(t, { + "name": "myname", + "scmRevision": "", + "version": "1.33.7", + "libraries": [], + }, oOptions); + + t.is(infoLogStub.callCount, 0); +}); diff --git a/packages/builder/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/packages/builder/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js new file mode 100644 index 00000000000..abea08068cc --- /dev/null +++ b/packages/builder/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -0,0 +1,254 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.log = { + info: sinon.stub() + }; + + t.context.ReaderCollectionPrioritizedStubClass = sinon.stub(); + t.context.fsInterfaceStub = sinon.stub().returns("custom fs"); + + t.context.sdkTransformerStub = sinon.stub().resolves(["resource A", "resource B"]); + + t.context.executeJsdocSdkTransformation = + await esmock("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation.js", { + "@ui5/fs/ReaderCollectionPrioritized": t.context.ReaderCollectionPrioritizedStubClass, + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "@ui5/logger": { + getLogger: sinon.stub().returns(t.context.log) + }, + "../../../../lib/processors/jsdoc/sdkTransformer.js": t.context.sdkTransformerStub + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("executeJsdocSdkTransformation", async (t) => { + const { + sinon, executeJsdocSdkTransformation, ReaderCollectionPrioritizedStubClass, + fsInterfaceStub, sdkTransformerStub + } = t.context; + + const writeStub = sinon.stub().resolves(); + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/.library" + }]); + const workspace = { + write: writeStub, + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + await executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + }); + t.is(byGlobWorkspaceStub.callCount, 2, "workspace.byGlob got called twice"); + t.is(byGlobWorkspaceStub.getCall(0).args[0], "/test-resources/**/designtime/api.json", + "first workspace.byGlob call with correct arguments"); + t.is(byGlobWorkspaceStub.getCall(1).args[0], "some .library pattern", + "second workspace.byGlob call with correct arguments"); + + t.is(byGlobDependenciesStub.callCount, 1, "dependencies.byGlob got called once"); + t.is(byGlobDependenciesStub.getCall(0).args[0], "/test-resources/**/designtime/api.json", + "dependencies.byGlob got called with correct arguments"); + + + t.is(ReaderCollectionPrioritizedStubClass.callCount, 1); + t.true(ReaderCollectionPrioritizedStubClass.calledWithNew()); + t.deepEqual(ReaderCollectionPrioritizedStubClass.getCall(0).args, [{ + name: "executeJsdocSdkTransformation - custom workspace + dependencies FS: some.project", + readers: [workspace, dependencies] + }], "ReaderCollectionPrioritized got called with the correct arguments"); + + t.is(fsInterfaceStub.callCount, 1, "fsInterface got called once"); + t.true(fsInterfaceStub.getCall(0).args[0] instanceof ReaderCollectionPrioritizedStubClass, + "fsInterface got called with an instance of ReaderCollectionPrioritizedStubClass"); + + t.is(sdkTransformerStub.callCount, 1, "sdkTransformer processor got called once"); + t.deepEqual(sdkTransformerStub.getCall(0).args[0], { + apiJsonPath: "workspace/api.json", + dotLibraryPath: "workspace/.library", + dependencyApiJsonPaths: [ + "depA/api.json", + "depB/api.json" + ], + targetApiJsonPath: "workspace/apiref/api.json", + fs: "custom fs" + }, "sdkTransformer got called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called twice"); + t.is(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.is(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); +}); + +test("executeJsdocSdkTransformation with missing parameters", async (t) => { + const {executeJsdocSdkTransformation} = t.context; + await t.throwsAsync(executeJsdocSdkTransformation(), { + instanceOf: TypeError + }, "TypeError thrown"); +}); + +test.serial("executeJsdocSdkTransformation with missing project api.json (skips processing)", async (t) => { + const {sinon, executeJsdocSdkTransformation, log} = t.context; + + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([]) + .onSecondCall().resolves([{ + getPath: () => "workspace/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + await executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + }); + + t.is(log.info.callCount, 1, "One message has been logged"); + t.is(log.info.getCall(0).args[0], + "Failed to locate api.json resource for project some.project. Skipping SDK Transformation...", + "Correct message has been logged"); +}); + +test("executeJsdocSdkTransformation too many project api.json resources", async (t) => { + const {sinon, executeJsdocSdkTransformation} = t.context; + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/a/api.json" + }, { + getPath: () => "workspace/b/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/a/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throwsAsync(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.is(error.message, + "[executeJsdocSdkTransformation]: Found more than one api.json resources for project some.project.", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation missing project .library", async (t) => { + const {sinon, executeJsdocSdkTransformation} = t.context; + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/api.json" + }]) + .onSecondCall().resolves([]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throwsAsync(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.is(error.message, + "[executeJsdocSdkTransformation]: Failed to locate .library resource for project some.project.", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation too many project .library resources", async (t) => { + const {sinon, executeJsdocSdkTransformation} = t.context; + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/a/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/a/.library" + }, { + getPath: () => "workspace/b/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throwsAsync(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.is(error.message, + "[executeJsdocSdkTransformation]: Found more than one .library resources for project some.project.", + "Correct error message thrown"); +}); diff --git a/packages/builder/test/lib/tasks/jsdoc/generateApiIndex.js b/packages/builder/test/lib/tasks/jsdoc/generateApiIndex.js new file mode 100644 index 00000000000..262e72fba92 --- /dev/null +++ b/packages/builder/test/lib/tasks/jsdoc/generateApiIndex.js @@ -0,0 +1,75 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.ReaderCollectionPrioritizedStubClass = sinon.stub(); + t.context.fsInterfaceStub = sinon.stub().returns("custom fs"); + + t.context.apiIndexGeneratorStub = sinon.stub().resolves(["resource A", "resource B"]); + + t.context.generateApiIndex = await esmock("../../../../lib/tasks/jsdoc/generateApiIndex.js", { + "@ui5/fs/ReaderCollectionPrioritized": t.context.ReaderCollectionPrioritizedStubClass, + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "../../../../lib/processors/jsdoc/apiIndexGenerator": t.context.apiIndexGeneratorStub + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("generateApiIndex", async (t) => { + const { + sinon, generateApiIndex, apiIndexGeneratorStub, + ReaderCollectionPrioritizedStubClass, fsInterfaceStub + } = t.context; + + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + const dependencies = {}; + await generateApiIndex({ + workspace, + dependencies, + options: { + projectName: "some.project" + } + }); + + t.is(ReaderCollectionPrioritizedStubClass.callCount, 1); + t.true(ReaderCollectionPrioritizedStubClass.calledWithNew()); + t.deepEqual(ReaderCollectionPrioritizedStubClass.getCall(0).args, [{ + name: "generateApiIndex - workspace + dependencies: some.project", + readers: [workspace, dependencies] + }], "ReaderCollectionPrioritized got called with the correct arguments"); + + t.is(fsInterfaceStub.callCount, 1, "fsInterface got called once"); + t.true(fsInterfaceStub.getCall(0).args[0] instanceof ReaderCollectionPrioritizedStubClass, + "fsInterface got called with an instance of ReaderCollectionPrioritizedStubClass"); + + t.is(apiIndexGeneratorStub.callCount, 1, "apiIndexGenerator processor got called once"); + t.deepEqual(apiIndexGeneratorStub.getCall(0).args[0], { + versionInfoPath: "/resources/sap-ui-version.json", + testResourcesRootPath: "/test-resources", + targetApiIndexPath: "/docs/api/api-index.json", + targetApiIndexDeprecatedPath: "/docs/api/api-index-deprecated.json", + targetApiIndexExperimentalPath: "/docs/api/api-index-experimental.json", + targetApiIndexSincePath: "/docs/api/api-index-since.json", + fs: "custom fs" + }, "apiIndexGenerator got called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called twice"); + t.is(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.is(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); +}); + +test("generateApiIndex with missing parameters", async (t) => { + const {generateApiIndex} = t.context; + await t.throwsAsync(generateApiIndex(), { + instanceOf: TypeError + }, "TypeError thrown"); +}); diff --git a/packages/builder/test/lib/tasks/jsdoc/generateJsdoc.js b/packages/builder/test/lib/tasks/jsdoc/generateJsdoc.js new file mode 100644 index 00000000000..31054cd3e7f --- /dev/null +++ b/packages/builder/test/lib/tasks/jsdoc/generateJsdoc.js @@ -0,0 +1,364 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import os from "node:os"; +import path from "node:path"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.mkdtempStub = sinon.stub(); + t.context.mkdirpStub = sinon.stub().resolves(); + t.context.rmrfStub = sinon.stub().resolves(); + t.context.jsdocGeneratorStub = sinon.stub(); + + t.context.writeStub = sinon.stub().resolves(); + t.context.createAdapterStub = sinon.stub().returns({ + write: t.context.writeStub + }); + + t.context.log = { + info: sinon.stub() + }; + + t.context.generateJsdoc = await esmock("../../../../lib/tasks/jsdoc/generateJsdoc.js", { + "graceful-fs": { + mkdtemp: t.context.mkdtempStub, + }, + "../../../../lib/utils/fs.js": { + mkdirp: t.context.mkdirpStub, + rmrf: t.context.rmrfStub + }, + "@ui5/fs/resourceFactory": { + createAdapter: t.context.createAdapterStub + }, + "@ui5/logger": { + getLogger: sinon.stub().returns(t.context.log) + }, + "../../../../lib/processors/jsdoc/jsdocGenerator": t.context.jsdocGeneratorStub, + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("createTmpDir successful", async (t) => { + const {generateJsdoc, mkdirpStub, mkdtempStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + mkdtempStub.callsArgWithAsync(1, undefined, "some/path"); + + const res = await generateJsdocUtils.createTmpDir("som$e.nam3/space"); // non alphanum characters get removed + + const tmpRootPath = path.join(os.tmpdir(), "ui5-cli"); + + t.is(mkdirpStub.callCount, 1, "One directory got created"); + t.deepEqual(mkdirpStub.getCall(0).args, [tmpRootPath], "Correct tmp root dir got created"); + + t.is(mkdtempStub.callCount, 1, "mkdtemp is called once"); + t.deepEqual(mkdtempStub.getCall(0).args[0], path.join(tmpRootPath, "jsdoc-somenam3space-")); + t.is(res, "some/path", "Correct path returned"); +}); + +test.serial("createTmpDir error", async (t) => { + const {generateJsdoc, mkdirpStub, mkdtempStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + mkdtempStub.callsArgWithAsync(1, new Error("Dir creation failed"), "some/path"); + + const res = await t.throwsAsync(generateJsdocUtils.createTmpDir("some.namespace")); + + const tmpRootPath = path.join(os.tmpdir(), "ui5-cli"); + + t.is(mkdirpStub.callCount, 1, "One directory got created"); + t.deepEqual(mkdirpStub.getCall(0).args, [tmpRootPath], "Correct tmp root dir got created"); + + t.is(mkdtempStub.callCount, 1, "mkdtemp is called once"); + t.deepEqual(mkdtempStub.getCall(0).args[0], path.join(tmpRootPath, "jsdoc-somenamespace-")); + t.is(res.message, "Dir creation failed", "Dir creation failed"); +}); + +test.serial("createTmpDirs", async (t) => { + const {sinon, generateJsdoc, mkdirpStub, rmrfStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + const createTmpDirStub = sinon.stub(generateJsdocUtils, "createTmpDir") + .resolves(path.join("/", "some", "path")); + + const res = await generateJsdocUtils.createTmpDirs("some.namespace"); + + t.is(createTmpDirStub.callCount, 1, "creteTmpDir called once"); + t.is(createTmpDirStub.getCall(0).args[0], "some.namespace", "creteTmpDir called with correct argument"); + + t.is(mkdirpStub.callCount, 3, "Three directory got created"); + t.deepEqual(mkdirpStub.getCall(0).args, [path.join("/", "some", "path", "src")], + "Correct srcdir path got created"); + t.deepEqual(mkdirpStub.getCall(1).args, [path.join("/", "some", "path", "target")], + "Correct target dir path got created"); + t.deepEqual(mkdirpStub.getCall(2).args, [path.join("/", "some", "path", "tmp")], + "Correct tmp dir path got created"); + + t.deepEqual(res.sourcePath, path.join("/", "some", "path", "src"), "Correct temporary src dir path returned"); + t.deepEqual(res.targetPath, path.join("/", "some", "path", "target"), "Correct temporary target dir path returned"); + t.deepEqual(res.tmpPath, path.join("/", "some", "path", "tmp"), "Correct temporary tmp dir path returned"); + + res.cleanup(); + t.is(rmrfStub.callCount, 1, "Cleanup callback: rmrf called once"); + t.deepEqual(rmrfStub.getCall(0).args[0], path.join("/", "some", "path"), + "Cleanup callback: rmrf called with correct path"); +}); + +test.serial("writeResourcesToDir with byGlob even if byGlobSource is available", async (t) => { + const {generateJsdoc, createAdapterStub, writeStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + await generateJsdocUtils.writeResourcesToDir({ + workspace: { + // stub byGlobSource + byGlobSource: (pattern) => { + throw new Error("Unexpected call to byGlobSource"); + }, + byGlob: (pattern) => { + t.is(pattern, "some pattern", "Glob with correct pattern"); + return Promise.resolve(["resource A", "resource B"]); + } + }, + pattern: "some pattern", + targetPath: path.join("/", "some", "target", "path") + }); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: path.join("/", "some", "target", "path"), + virBasePath: "/resources/" + }, "createAdapter called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called four times"); + t.is(writeStub.getCall(0).args[0], "resource A", "Write got called for resource A"); + t.is(writeStub.getCall(1).args[0], "resource B", "Write got called for resource B"); +}); + +test.serial("writeResourcesToDir with byGlob", async (t) => { + const {generateJsdoc, createAdapterStub, writeStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + await generateJsdocUtils.writeResourcesToDir({ + workspace: { + byGlob: (pattern) => { + t.is(pattern, "some pattern", "Glob with correct pattern"); + return Promise.resolve(["resource A", "resource B"]); + } + }, + pattern: "some pattern", + targetPath: path.join("/", "some", "target", "path") + }); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: path.join("/", "some", "target", "path"), + virBasePath: "/resources/" + }, "createAdapter called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called four times"); + t.is(writeStub.getCall(0).args[0], "resource A", "Write got called for resource A"); + t.is(writeStub.getCall(1).args[0], "resource B", "Write got called for resource B"); +}); + +test.serial("writeDependencyApisToDir with byGlob", async (t) => { + const {sinon, generateJsdoc, createAdapterStub, writeStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + const setPathStubA = sinon.stub(); + const setPathStubB = sinon.stub(); + + const cloneStubA = sinon.stub().resolves({ + // Cloned resource + id: "resource A", + setPath: setPathStubA + }); + const cloneStubB = sinon.stub().resolves({ + // Cloned resource + id: "resource B", + setPath: setPathStubB + }); + const initialResourceA = { + // Globbed resource + clone: cloneStubA + }; + const initialResourceB = { + // Globbed resource + clone: cloneStubB + }; + + await generateJsdocUtils.writeDependencyApisToDir({ + dependencies: { + byGlob: (pattern) => { + t.is(pattern, "/test-resources/**/designtime/api.json", + "Dependency api.json glob with correct pattern"); + return Promise.resolve([initialResourceA, initialResourceB]); + } + }, + targetPath: path.join("/", "some", "target", "path") + }); + + t.is(cloneStubA.callCount, 1, "resource A got cloned once"); + t.is(cloneStubB.callCount, 1, "resource B got cloned once"); + + t.is(setPathStubA.callCount, 1, "Path of cloned resource A got changed"); + t.is(setPathStubA.getCall(0).args[0], "/api-0.json", "Path of cloned resource A got changed correctly"); + + t.is(setPathStubB.callCount, 1, "Path of cloned resource B got changed"); + t.is(setPathStubB.getCall(0).args[0], "/api-1.json", "Path of cloned resource B got changed correctly"); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: path.join("/", "some", "target", "path"), + virBasePath: "/" + }, "createAdapter called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called four times"); + t.is(writeStub.getCall(0).args[0].id, "resource A", "Write got called for resource A"); + t.is(writeStub.getCall(1).args[0].id, "resource B", "Write got called for resource B"); +}); + +test.serial("generateJsdoc", async (t) => { + const {sinon, generateJsdoc, jsdocGeneratorStub} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + jsdocGeneratorStub.resolves(["resource A", "resource B"]); + + const cleanupStub = sinon.stub().resolves(); + const createTmpDirsStub = sinon.stub(generateJsdocUtils, "createTmpDirs").resolves({ + sourcePath: path.join("/", "some", "source", "path"), + targetPath: path.join("/", "some", "target", "path"), + tmpPath: path.join("/", "some", "tmp", "path"), + cleanup: cleanupStub + }); + const writeResourcesToDirStub = sinon.stub(generateJsdocUtils, "writeResourcesToDir").resolves(1); + const writeDependencyApisToDirStub = sinon.stub(generateJsdocUtils, "writeDependencyApisToDir").resolves(0); + + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + + const registerCleanupTaskStub = sinon.stub(); + const taskUtil = { + registerCleanupTask: registerCleanupTaskStub + }; + await generateJsdoc({ + taskUtil, + workspace, + dependencies: "dependencies", + options: { + pattern: "some pattern", + projectName: "some.project", + namespace: "some/project", + version: "some version" + } + }); + + t.is(createTmpDirsStub.callCount, 1, "createTmpDirs got called once"); + t.is(createTmpDirsStub.getCall(0).args[0], "some.project", + "createTmpDirs got called with correct arguments"); + + t.is(registerCleanupTaskStub.callCount, 1, "registerCleanupTask called once"); + t.deepEqual(registerCleanupTaskStub.getCall(0).args[0], cleanupStub, + "registerCleanupTask called with correct argument"); + + t.is(writeResourcesToDirStub.callCount, 1, "writeResourcesToDir got called once"); + t.deepEqual(writeResourcesToDirStub.getCall(0).args[0], { + workspace, + pattern: "some pattern", + targetPath: path.join("/", "some", "source", "path") // one's target is another one's source + }, "writeResourcesToDir got called with correct arguments"); + + t.is(writeDependencyApisToDirStub.callCount, 1, "writeDependencyApisToDir got called once"); + t.deepEqual(writeDependencyApisToDirStub.getCall(0).args[0], { + dependencies: "dependencies", + targetPath: path.join("/", "some", "tmp", "path", "dependency-apis") + }, "writeDependencyApisToDir got called with correct arguments"); + + t.is(jsdocGeneratorStub.callCount, 1, "jsdocGenerator processor got called once"); + t.deepEqual(jsdocGeneratorStub.getCall(0).args[0], { + sourcePath: path.join("/", "some", "source", "path"), + targetPath: path.join("/", "some", "target", "path"), + tmpPath: path.join("/", "some", "tmp", "path"), + options: { + projectName: "some.project", + namespace: "some/project", + version: "some version", + variants: ["apijson"] + } + }, "jsdocGenerator got called with correct arguments"); + + t.is(writeStub.callCount, 2, "Write got called twice"); + t.is(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.is(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = registerCleanupTaskStub.getCall(0).args[0]; + await cleanupTask(); +}); + +test.serial("generateJsdoc with missing resources", async (t) => { + const {sinon, jsdocGeneratorStub, generateJsdoc, log} = t.context; + const generateJsdocUtils = generateJsdoc._utils; + + const cleanupStub = sinon.stub().resolves(); + sinon.stub(generateJsdocUtils, "createTmpDirs").resolves({ + sourcePath: path.join("/", "some", "source", "path"), + targetPath: path.join("/", "some", "target", "path"), + tmpPath: path.join("/", "some", "tmp", "path"), + cleanup: cleanupStub + }); + sinon.stub(generateJsdocUtils, "writeResourcesToDir").resolves(0); + sinon.stub(generateJsdocUtils, "writeDependencyApisToDir").resolves(0); + + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + const registerCleanupTaskStub = sinon.stub(); + const taskUtil = { + registerCleanupTask: registerCleanupTaskStub + }; + await generateJsdoc({ + taskUtil, + workspace, + dependencies: "dependencies", + options: { + pattern: "some pattern", + projectName: "some.project", + namespace: "some/project", + version: "some version" + } + }); + + t.is(registerCleanupTaskStub.callCount, 1, "registerCleanupTask called once"); + t.deepEqual(registerCleanupTaskStub.getCall(0).args[0], cleanupStub, + "registerCleanupTask called with correct argument"); + + t.is(log.info.callCount, 1, "One message has been logged"); + t.deepEqual(log.info.getCall(0).args[0], "Failed to find any input resources for project some.project " + + "using pattern some pattern. Skipping JSDoc generation...", + "Correct message has been logged"); + + t.is(jsdocGeneratorStub.callCount, 0, "jsdocGenerator processor has *not* been called"); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = registerCleanupTaskStub.getCall(0).args[0]; + await cleanupTask(); +}); + +test.serial("generateJsdoc no parameters", async (t) => { + const {generateJsdoc} = t.context; + await t.throwsAsync(generateJsdoc(), { + instanceOf: TypeError + }, "TypeError thrown"); +}); + +test.serial("generateJsdoc missing parameters", async (t) => { + const {generateJsdoc} = t.context; + const error = await t.throwsAsync(generateJsdoc({})); + t.is(error.message, "[generateJsdoc]: One or more mandatory options not provided", + "Correct error message thrown"); +}); diff --git a/packages/builder/test/lib/tasks/minify.integration.js b/packages/builder/test/lib/tasks/minify.integration.js new file mode 100644 index 00000000000..ef26c411df7 --- /dev/null +++ b/packages/builder/test/lib/tasks/minify.integration.js @@ -0,0 +1,434 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import minify from "../../../lib/tasks/minify.js"; +import * as resourceFactory from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +// Node.js itself tries to parse sourceMappingURLs in all JavaScript files. This is unwanted and might even lead to +// obscure errors when dynamically generating Data-URI soruceMappingURL values. +// Therefore use this constant to never write the actual string. +const SOURCE_MAPPING_URL = "//" + "# sourceMappingURL"; + +function createWorkspace() { + const reader = resourceFactory.createAdapter({ + virBasePath: "/" + }); + const writer = resourceFactory.createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader: reader, writer: writer}); + return {reader, writer, workspace}; +} + +test.beforeEach((t) => { + t.context.sinon = sinonGlobal.createSandbox(); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + + +test.serial("integration: minify omitSourceMapResources=true", async (t) => { + const taskUtil = { + setTag: t.context.sinon.stub(), + getTag: t.context.sinon.stub().returns(false), + STANDARD_TAGS: { + HasDebugVariant: "1️⃣", + IsDebugVariant: "2️⃣", + OmitFromBuildResult: "3️⃣" + }, + registerCleanupTask: t.context.sinon.stub() + }; + const {reader, writer, workspace} = createWorkspace(); + const content = ` +function test(paramA) { + var variableA = paramA; + console.log(variableA); +} +test();`; + const testResource = resourceFactory.createResource({ + path: "/test.js", + string: content + }); + await reader.write(testResource); + + await minify({ + workspace, + taskUtil, + options: { + pattern: "/test.js", + omitSourceMapResources: true + } + }); + + const expected = `function test(t){var o=t;console.log(o)}test();`; + const res = await writer.byPath("/test.js"); + if (!res) { + t.fail("Could not find /test.js in target locator"); + } + t.deepEqual(await res.getString(), expected, "Correct file content"); + + const resDbg = await writer.byPath("/test-dbg.js"); + if (!resDbg) { + t.fail("Could not find /test-dbg.js in target locator"); + } + t.deepEqual(await resDbg.getString(), content, "Correct debug-file content"); + + const expectedSourceMap = + `{"version":3,"file":"test.js",` + + `"names":["test","paramA","variableA","console","log"],"sources":["test-dbg.js"],` + + `"mappings":"AACA,SAASA,KAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF","ignoreList":[]}`; + + const resSourceMap = await writer.byPath("/test.js.map"); + if (!resSourceMap) { + t.fail("Could not find /test-dbg.js.map in target locator"); + } + t.deepEqual(await resSourceMap.getString(), expectedSourceMap, "Correct source map content"); + + t.is(taskUtil.setTag.callCount, 4, "taskUtil.setTag was called 4 times"); + t.is(taskUtil.setTag.getCall(0).args[0].getPath(), res.getPath(), + "First taskUtil.setTag call with expected first argument"); + t.is(taskUtil.setTag.getCall(0).args[1], "1️⃣", "First taskUtil.setTag call with expected second argument"); + t.is(taskUtil.setTag.getCall(1).args[0].getPath(), resDbg.getPath(), + "Second taskUtil.setTag call with expected first arguments"); + t.is(taskUtil.setTag.getCall(1).args[1], "2️⃣", + "Second taskUtil.setTag call with expected second arguments"); + t.is(taskUtil.setTag.getCall(2).args[0].getPath(), resSourceMap.getPath(), + "Third taskUtil.setTag call with expected first arguments"); + t.is(taskUtil.setTag.getCall(2).args[1], "1️⃣", + "Third taskUtil.setTag call with expected second arguments"); + t.is(taskUtil.setTag.getCall(3).args[0].getPath(), resSourceMap.getPath(), + "Fourth taskUtil.setTag call with expected first arguments"); + t.is(taskUtil.setTag.getCall(3).args[1], "3️⃣", + "Fourth taskUtil.setTag call with expected second arguments"); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtil.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); + +test.serial("integration: minify omitSourceMapResources=false", async (t) => { + const taskUtil = { + setTag: t.context.sinon.stub(), + getTag: t.context.sinon.stub().returns(false), + STANDARD_TAGS: { + HasDebugVariant: "1️⃣", + IsDebugVariant: "2️⃣", + OmitFromBuildResult: "3️⃣" + }, + registerCleanupTask: t.context.sinon.stub() + }; + const {reader, writer, workspace} = createWorkspace(); + const content = ` +function test(paramA) { + var variableA = paramA; + console.log(variableA); +} +test();`; + const testResource = resourceFactory.createResource({ + path: "/test.js", + string: content + }); + await reader.write(testResource); + + await minify({ + workspace, + taskUtil, + options: { + pattern: "/test.js" + } + }); + + const expected = `function test(t){var o=t;console.log(o)}test(); +${SOURCE_MAPPING_URL}=test.js.map`; + const res = await writer.byPath("/test.js"); + if (!res) { + t.fail("Could not find /test.js in target locator"); + } + t.deepEqual(await res.getString(), expected, "Correct file content"); + + const resDbg = await writer.byPath("/test-dbg.js"); + if (!resDbg) { + t.fail("Could not find /test-dbg.js in target locator"); + } + t.deepEqual(await resDbg.getString(), content, "Correct debug-file content"); + + const expectedSourceMap = + `{"version":3,"file":"test.js",` + + `"names":["test","paramA","variableA","console","log"],"sources":["test-dbg.js"],` + + `"mappings":"AACA,SAASA,KAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF","ignoreList":[]}`; + + const resSourceMap = await writer.byPath("/test.js.map"); + if (!resSourceMap) { + t.fail("Could not find /test-dbg.js.map in target locator"); + } + t.deepEqual(await resSourceMap.getString(), expectedSourceMap, "Correct source map content"); + + t.is(taskUtil.setTag.callCount, 3, "taskUtil.setTag was called 3 times"); + t.is(taskUtil.setTag.getCall(0).args[0].getPath(), res.getPath(), + "First taskUtil.setTag call with expected first argument"); + t.is(taskUtil.setTag.getCall(0).args[1], "1️⃣", "First taskUtil.setTag call with expected second argument"); + t.is(taskUtil.setTag.getCall(1).args[0].getPath(), resDbg.getPath(), + "Second taskUtil.setTag call with expected first arguments"); + t.is(taskUtil.setTag.getCall(1).args[1], "2️⃣", + "Second taskUtil.setTag call with expected second arguments"); + t.is(taskUtil.setTag.getCall(2).args[0].getPath(), resSourceMap.getPath(), + "Third taskUtil.setTag call with expected first arguments"); + t.is(taskUtil.setTag.getCall(2).args[1], "1️⃣", + "Third taskUtil.setTag call with expected second arguments"); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtil.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); + +test("integration: minify omitSourceMapResources=true (without taskUtil)", async (t) => { + const {reader, writer, workspace} = createWorkspace(); + const content = ` +function test(paramA) { + var variableA = paramA; + console.log(variableA); +} +test();`; + const testResource = resourceFactory.createResource({ + path: "/test.js", + string: content + }); + await reader.write(testResource); + + await minify({ + workspace, + options: { + pattern: "/test.js", + omitSourceMapResources: true + } + }); + + const expected = `function test(t){var o=t;console.log(o)}test();`; + const res = await writer.byPath("/test.js"); + if (!res) { + t.fail("Could not find /test.js in target locator"); + } + t.deepEqual(await res.getString(), expected, "Correct file content"); + + const resDbg = await writer.byPath("/test-dbg.js"); + if (!resDbg) { + t.fail("Could not find /test-dbg.js in target locator"); + } + t.deepEqual(await resDbg.getString(), content, "Correct debug-file content"); + + const expectedSourceMap = + `{"version":3,"file":"test.js",` + + `"names":["test","paramA","variableA","console","log"],"sources":["test-dbg.js"],` + + `"mappings":"AACA,SAASA,KAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF","ignoreList":[]}`; + + const resSourceMap = await writer.byPath("/test.js.map"); + if (!resSourceMap) { + t.fail("Could not find /test-dbg.js.map in target locator"); + } + t.deepEqual(await resSourceMap.getString(), expectedSourceMap, "Correct source map content"); +}); + +test("integration: minify omitSourceMapResources=false (without taskUtil)", async (t) => { + const {reader, writer, workspace} = createWorkspace(); + const content = ` +function test(paramA) { + var variableA = paramA; + console.log(variableA); +} +test();`; + const testResource = resourceFactory.createResource({ + path: "/test.js", + string: content + }); + await reader.write(testResource); + + await minify({ + workspace, + options: { + pattern: "/test.js", + omitSourceMapResources: false + } + }); + + const expected = `function test(t){var o=t;console.log(o)}test(); +${SOURCE_MAPPING_URL}=test.js.map`; + const res = await writer.byPath("/test.js"); + if (!res) { + t.fail("Could not find /test.js in target locator"); + } + t.deepEqual(await res.getString(), expected, "Correct file content"); + + const resDbg = await writer.byPath("/test-dbg.js"); + if (!resDbg) { + t.fail("Could not find /test-dbg.js in target locator"); + } + t.deepEqual(await resDbg.getString(), content, "Correct debug-file content"); + + const expectedSourceMap = + `{"version":3,"file":"test.js",` + + `"names":["test","paramA","variableA","console","log"],"sources":["test-dbg.js"],` + + `"mappings":"AACA,SAASA,KAAKC,GACb,IAAIC,EAAYD,EAChBE,QAAQC,IAAIF,EACb,CACAF","ignoreList":[]}`; + + const resSourceMap = await writer.byPath("/test.js.map"); + if (!resSourceMap) { + t.fail("Could not find /test-dbg.js.map in target locator"); + } + t.deepEqual(await resSourceMap.getString(), expectedSourceMap, "Correct source map content"); +}); + +test.serial("integration: minify error", async (t) => { + const taskUtil = { + setTag: t.context.sinon.stub(), + getTag: t.context.sinon.stub().returns(false), + STANDARD_TAGS: { + HasDebugVariant: "1️⃣", + IsDebugVariant: "2️⃣", + OmitFromBuildResult: "3️⃣" + }, + registerCleanupTask: t.context.sinon.stub() + }; + const {reader, workspace} = createWorkspace(); + const content = ` +// Top level return will cause a parsing error +return;`; + const testResource = resourceFactory.createResource({ + path: "/resources/my/namespace/test.js", + string: content + }); + await reader.write(testResource); + + await t.throwsAsync(() => { + return minify({ + workspace, + taskUtil, + options: { + pattern: "/resources/my/namespace/test.js", + omitSourceMapResources: true + } + }); + }, { + message: + `Minification failed with error: 'return' outside of function in file ` + + `/resources/my/namespace/test.js (line 3, col 0, pos 48)` + }, `Threw with expected error message`); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtil.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); + + +test.serial("integration: minify error (without taskUtil)", async (t) => { + const {reader, workspace} = createWorkspace(); + const content = ` +// Top level return will cause a parsing error +return;`; + const testResource = resourceFactory.createResource({ + path: "/resources/my/namespace/test.js", + string: content + }); + await reader.write(testResource); + + await t.throwsAsync(() => { + return minify({ + workspace, + options: { + pattern: "/resources/my/namespace/test.js", + omitSourceMapResources: true + } + }); + }, { + message: + `Minification failed with error: 'return' outside of function in file ` + + `/resources/my/namespace/test.js (line 3, col 0, pos 48)` + }, `Threw with expected error message`); +}); + +test.serial("integration: minify with taskUtil and resources tagged with OmitFromBuildResult", async (t) => { + const {reader, workspace} = createWorkspace(); + + const testFilePath1 = "/resources/my/namespace/test1.js"; + const testFilePath2 = "/resources/my/namespace/test2.js"; + const testFileContent1 = "function test(param1) { var variableA = param1; console.log(variableA); } test();"; + const testFileContent2 = "function test(param2) { var variableB = param2; console.log(variableB); } test();"; + + const testResource1 = resourceFactory.createResource({ + path: testFilePath1, + string: testFileContent1 + }); + const testResource2 = resourceFactory.createResource({ + path: testFilePath2, + string: testFileContent2 + }); + + await reader.write(testResource1); + await reader.write(testResource2); + + const taskUtil = { + STANDARD_TAGS: { + HasDebugVariant: "1️⃣", + IsDebugVariant: "2️⃣", + OmitFromBuildResult: "3️⃣" + }, + setTag: t.context.sinon.stub(), + getTag: t.context.sinon.stub().callsFake((resource, tag) => { + if (resource.getPath() === testFilePath1 && + tag === taskUtil.STANDARD_TAGS.OmitFromBuildResult) { + return true; // OmitFromBuildResult for testFilePath1 + } + return false; // No OmitFromBuildResult for testFilePath2 + }), + registerCleanupTask: t.context.sinon.stub() + }; + + await minify({ + workspace, + taskUtil, + options: { + pattern: "/**/*.js", + } + }); + + t.is(taskUtil.setTag.callCount, 8, "taskUtil.setTag was called 8 times"); + + const taggedResources = []; + for (const call of taskUtil.setTag.getCalls()) { + const resourcePath = call.args[0].getPath(); + const tag = call.args[1]; + taggedResources.push({resourcePath, tag}); + } + + taggedResources.sort((a, b) => a.resourcePath.localeCompare(b.resourcePath)); + + t.deepEqual(taggedResources, [{ + resourcePath: "/resources/my/namespace/test1-dbg.js", + tag: taskUtil.STANDARD_TAGS.OmitFromBuildResult, + }, { + resourcePath: "/resources/my/namespace/test1-dbg.js", + tag: taskUtil.STANDARD_TAGS.IsDebugVariant, + }, { + resourcePath: "/resources/my/namespace/test1.js", + tag: taskUtil.STANDARD_TAGS.HasDebugVariant, + }, { + resourcePath: "/resources/my/namespace/test1.js.map", + tag: taskUtil.STANDARD_TAGS.OmitFromBuildResult, + }, { + resourcePath: "/resources/my/namespace/test1.js.map", + tag: taskUtil.STANDARD_TAGS.HasDebugVariant, + }, { + resourcePath: "/resources/my/namespace/test2-dbg.js", + tag: taskUtil.STANDARD_TAGS.IsDebugVariant, + }, { + resourcePath: "/resources/my/namespace/test2.js", + tag: taskUtil.STANDARD_TAGS.HasDebugVariant, + }, { + resourcePath: "/resources/my/namespace/test2.js.map", + tag: taskUtil.STANDARD_TAGS.HasDebugVariant, + }], "Correct tags set on resources"); + + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = taskUtil.registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); +}); diff --git a/packages/builder/test/lib/tasks/minify.js b/packages/builder/test/lib/tasks/minify.js new file mode 100644 index 00000000000..1edc139c0b7 --- /dev/null +++ b/packages/builder/test/lib/tasks/minify.js @@ -0,0 +1,212 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + t.context.workspace = { + byGlob: sinon.stub().resolves(["resource A", "resource B"]), + write: sinon.stub().resolves() + }; + t.context.taskUtil = { + setTag: sinon.stub(), + getTag: sinon.stub(), + STANDARD_TAGS: { + HasDebugVariant: "has debug variant", + IsDebugVariant: "is debug variant", + OmitFromBuildResult: "omit from build result" + }, + registerCleanupTask: sinon.stub() + }; + + t.context.fsInterfaceStub = sinon.stub().returns("fs interface"); + t.context.minifierStub = sinon.stub(); + t.context.minify = await esmock("../../../lib/tasks/minify.js", { + "@ui5/fs/fsInterface": t.context.fsInterfaceStub, + "../../../lib/processors/minifier.js": t.context.minifierStub + }); +}); +test.afterEach.always(async (t) => { + const {registerCleanupTask} = t.context.taskUtil; + + if (registerCleanupTask.callCount === 1) { + // Ensure to call cleanup task so that workerpool is terminated - otherwise the test will time out! + const cleanupTask = registerCleanupTask.getCall(0).args[0]; + await cleanupTask(); + } + + t.context.sinon.restore(); +}); + +test("minify: Default params", async (t) => { + const {minify, workspace, taskUtil, minifierStub} = t.context; + minifierStub.resolves([{ + resource: "resource A", + dbgResource: "dbgResource A", + sourceMapResource: "sourceMapResource A", + dbgSourceMapResource: "dbgSourceMapResource A" // optional + }, { + resource: "resource B", + dbgResource: "dbgResource B", + sourceMapResource: "sourceMapResource B", + }]); + await minify({ + workspace, + taskUtil, + options: { + pattern: "**" + } + }); + + t.is(minifierStub.callCount, 1, "minifier got called once"); + const minifierCallArgs = minifierStub.firstCall.firstArg; + t.deepEqual(minifierCallArgs.resources, ["resource A", "resource B"], "Correct resources provided to processor"); + t.is(minifierCallArgs.fs, "fs interface", "Correct fs interface provided to processor"); + t.is(minifierCallArgs.taskUtil, taskUtil, "Correct taskUtil provided to processor"); + t.deepEqual(minifierCallArgs.options, { + addSourceMappingUrl: true, + readSourceMappingUrl: true, + useWorkers: true + }, "minifier got called with expected options"); + + t.is(taskUtil.setTag.callCount, 7, "taskUtil#setTag got called 12 times"); + t.is(taskUtil.setTag.getCall(0).args[0], "resource A", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(0).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(1).args[0], "dbgResource A", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(1).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(2).args[0], "sourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(2).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(3).args[0], "dbgSourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(3).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + + t.is(taskUtil.setTag.getCall(4).args[0], "resource B", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(4).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(5).args[0], "dbgResource B", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(5).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(6).args[0], "sourceMapResource B", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(6).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + + t.is(workspace.write.callCount, 7, "workspace#write got called seven times"); + [ + "resource A", "dbgResource A", "sourceMapResource A", "dbgSourceMapResource A", + "resource B", "dbgResource B", "sourceMapResource B" + ].forEach((resName, idx) => { + t.is(workspace.write.getCall(idx).firstArg, resName, "workspace#write got called for expected resource"); + }); +}); + +test("minify: omitSourceMapResources: true, useInputSourceMaps: false", async (t) => { + const {minify, workspace, taskUtil, minifierStub} = t.context; + minifierStub.resolves([{ + resource: "resource A", + dbgResource: "dbgResource A", + sourceMapResource: "sourceMapResource A", + dbgSourceMapResource: "dbgSourceMapResource A" // optional + }, { + resource: "resource B", + dbgResource: "dbgResource B", + sourceMapResource: "sourceMapResource B", + }]); + await minify({ + workspace, + taskUtil, + options: { + pattern: "**", + omitSourceMapResources: true, + useInputSourceMaps: false + } + }); + + t.is(minifierStub.callCount, 1, "minifier got called once"); + const minifierCallArgs = minifierStub.firstCall.firstArg; + t.deepEqual(minifierCallArgs.resources, ["resource A", "resource B"], "Correct resources provided to processor"); + t.is(minifierCallArgs.fs, "fs interface", "Correct fs interface provided to processor"); + t.is(minifierCallArgs.taskUtil, taskUtil, "Correct taskUtil provided to processor"); + t.deepEqual(minifierCallArgs.options, { + addSourceMappingUrl: false, + readSourceMappingUrl: false, + useWorkers: true + }, "minifier got called with expected options"); + + t.is(taskUtil.setTag.callCount, 10, "taskUtil#setTag got called 12 times"); + t.is(taskUtil.setTag.getCall(0).args[0], "resource A", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(0).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(1).args[0], "dbgResource A", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(1).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(2).args[0], "sourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(2).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(3).args[0], "sourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(3).args[1], "omit from build result", + "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(4).args[0], "dbgSourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(4).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(5).args[0], "dbgSourceMapResource A", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(5).args[1], "omit from build result", + "taskUtil#setTag got called with the correct tag"); + + t.is(taskUtil.setTag.getCall(6).args[0], "resource B", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(6).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(7).args[0], "dbgResource B", "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(7).args[1], "is debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(8).args[0], "sourceMapResource B", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(8).args[1], "has debug variant", "taskUtil#setTag got called with the correct tag"); + t.is(taskUtil.setTag.getCall(9).args[0], "sourceMapResource B", + "taskUtil#setTag got called with the correct resource"); + t.is(taskUtil.setTag.getCall(9).args[1], "omit from build result", + "taskUtil#setTag got called with the correct tag"); + + t.is(workspace.write.callCount, 7, "workspace#write got called seven times"); + [ + "resource A", "dbgResource A", "sourceMapResource A", "dbgSourceMapResource A", + "resource B", "dbgResource B", "sourceMapResource B" + ].forEach((resName, idx) => { + t.is(workspace.write.getCall(idx).firstArg, resName, "workspace#write got called for expected resource"); + }); +}); + +test("minify: No taskUtil", async (t) => { + const {minify, workspace, minifierStub} = t.context; + minifierStub.resolves([{ + resource: "resource A", + dbgResource: "dbgResource A", + sourceMapResource: "sourceMapResource A", + dbgSourceMapResource: "dbgSourceMapResource A" // optional + }, { + resource: "resource B", + dbgResource: "dbgResource B", + sourceMapResource: "sourceMapResource B", + }]); + await minify({ + workspace, + options: { + pattern: "**" + } + }); + + t.is(minifierStub.callCount, 1, "minifier got called once"); + const minifierCallArgs = minifierStub.firstCall.firstArg; + t.deepEqual(minifierCallArgs.resources, ["resource A", "resource B"], "Correct resources provided to processor"); + t.is(minifierCallArgs.fs, "fs interface", "Correct fs interface provided to processor"); + t.is(minifierCallArgs.taskUtil, undefined, "No taskUtil provided to processor"); + t.deepEqual(minifierCallArgs.options, { + addSourceMappingUrl: true, + readSourceMappingUrl: true, + useWorkers: false + }, "minifier got called with expected options"); + + t.is(workspace.write.callCount, 7, "workspace#write got called seven times"); + [ + "resource A", "dbgResource A", "sourceMapResource A", "dbgSourceMapResource A", + "resource B", "dbgResource B", "sourceMapResource B" + ].forEach((resName, idx) => { + t.is(workspace.write.getCall(idx).firstArg, resName, "workspace#write got called for expected resource"); + }); +}); diff --git a/packages/builder/test/lib/tasks/replaceBuildtime.js b/packages/builder/test/lib/tasks/replaceBuildtime.js new file mode 100644 index 00000000000..129af3f31ad --- /dev/null +++ b/packages/builder/test/lib/tasks/replaceBuildtime.js @@ -0,0 +1,44 @@ +import test from "ava"; +import replaceBuildtime from "../../../lib/tasks/replaceBuildtime.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +test("integration: replace version", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + + const content = "// timestamp: ${buildtime}"; + const expectedPrefix = "// timestamp"; + const expectedDatePattern = /^\d{8}-\d{4}$/; + + const resource = createResource({ + path: "/test.js", + string: content + }); + + const workspace = new DuplexCollection({reader, writer}); + await reader.write(resource); + await replaceBuildtime({ + workspace, + options: { + pattern: "/test.js" + } + }); + const transformedResource = await writer.byPath("/test.js"); + + if (!transformedResource) { + t.fail("Could not find /test.js in target"); + } else { + const buffer = await transformedResource.getBuffer(); + const actualContent = buffer.toString(); + t.not(actualContent, content, "placeholder is overridden"); + + const values = actualContent.split(": "); + t.is(values[0], expectedPrefix, "prefix is unmodified"); + t.regex(values[1], expectedDatePattern, "date matches the given pattern"); + } +}); diff --git a/packages/builder/test/lib/tasks/replaceCopyright.js b/packages/builder/test/lib/tasks/replaceCopyright.js new file mode 100644 index 00000000000..5aa7c2cd89b --- /dev/null +++ b/packages/builder/test/lib/tasks/replaceCopyright.js @@ -0,0 +1,104 @@ +import test from "ava"; +import replaceCopyright from "../../../lib/tasks/replaceCopyright.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +test("integration: replace copyright", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + + /* eslint-disable no-useless-escape */ + const content = `/*! + * $\{copyright\} + */ +console.log('HelloWorld');`; + /* eslint-enable no-useless-escape */ + + const copyright = `UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-\${currentYear} SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.`; + + const year = new Date().getFullYear(); + const expected = `/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-${year} SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log('HelloWorld');`; + + const resource = createResource({ + path: "/test.js", + string: content + }); + + await workspace.write(resource); + + await replaceCopyright({ + workspace, + options: { + copyright: copyright, + pattern: "/**/*.js" + } + }); + + const transformedResource = await writer.byPath("/test.js"); + + if (!transformedResource) { + t.fail("Could not find /test.js in target"); + } else { + t.deepEqual(await transformedResource.getString(), expected); + } +}); + + +test("test.xml: replace @copyright@", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + const workspace = new DuplexCollection({reader, writer}); + const content = ` +`; + + const copyright = `UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-\${currentYear} SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.`; + + const year = new Date().getFullYear(); + const expected = ` +`; + + const resource = createResource({ + path: "/test.xml", + string: content + }); + + await reader.write(resource); + await replaceCopyright({ + workspace, + options: { + pattern: "/**/*.xml", + copyright: copyright + } + }); + const transformedResource = await writer.byPath("/test.xml"); + + if (!transformedResource) { + t.fail("Could not find /test.xml in target"); + } else { + t.deepEqual(await transformedResource.getString(), expected); + } +}); diff --git a/packages/builder/test/lib/tasks/replaceVersion.js b/packages/builder/test/lib/tasks/replaceVersion.js new file mode 100644 index 00000000000..6d831610dd4 --- /dev/null +++ b/packages/builder/test/lib/tasks/replaceVersion.js @@ -0,0 +1,39 @@ +import test from "ava"; +import replaceVersion from "../../../lib/tasks/replaceVersion.js"; +import {createAdapter, createResource} from "@ui5/fs/resourceFactory"; +import DuplexCollection from "@ui5/fs/DuplexCollection"; + +test("integration: replace version", async (t) => { + const reader = createAdapter({ + virBasePath: "/" + }); + const writer = createAdapter({ + virBasePath: "/" + }); + + const content = "console.log('${version} equals ${project.version}');"; + const expected = "console.log('1.337.0 equals 1.337.0');"; + + const resource = createResource({ + path: "/test.js", + string: content + }); + + const workspace = new DuplexCollection({reader, writer}); + await reader.write(resource); + await replaceVersion({ + workspace, + options: { + pattern: "/test.js", + version: "1.337.0" + } + }); + + const transformedResource = await writer.byPath("/test.js"); + if (!transformedResource) { + t.fail("Could not find /test.js in target"); + } else { + const buffer = await transformedResource.getBuffer(); + t.deepEqual(buffer.toString(), expected); + } +}); diff --git a/packages/builder/test/lib/tasks/taskRepository.js b/packages/builder/test/lib/tasks/taskRepository.js new file mode 100644 index 00000000000..616d7e9e197 --- /dev/null +++ b/packages/builder/test/lib/tasks/taskRepository.js @@ -0,0 +1,76 @@ +import test from "ava"; +import semver from "semver"; +import {getTask, getAllTaskNames, getRemovedTaskNames, getVersions} from "../../../lib/tasks/taskRepository.js"; + +test("Task retrieval", async (t) => { + const escapeNonAsciiCharacters = (await import("../../../lib/tasks/escapeNonAsciiCharacters.js")).default; + const taskInfoPromise = getTask("escapeNonAsciiCharacters"); + t.true(taskInfoPromise instanceof Promise); + const taskInfo = await taskInfoPromise; + t.deepEqual(taskInfo, { + task: escapeNonAsciiCharacters + }, "Expected task retrieved"); +}); + +test("getAllTaskNames", (t) => { + const taskNames = getAllTaskNames(); + t.deepEqual(taskNames, [ + "replaceCopyright", + "replaceVersion", + "replaceBuildtime", + "enhanceManifest", + "escapeNonAsciiCharacters", + "executeJsdocSdkTransformation", + "generateApiIndex", + "generateJsdoc", + "minify", + "buildThemes", + "transformBootstrapHtml", + "generateLibraryManifest", + "generateVersionInfo", + "generateFlexChangesBundle", + "generateComponentPreload", + "generateResourcesJson", + "generateThemeDesignerResources", + "generateStandaloneAppBundle", + "generateBundle", + "generateLibraryPreload", + "generateCachebusterInfo", + ], "Returned list of all standard tasks"); +}); + +test("Unknown task retrieval", async (t) => { + const error = await t.throwsAsync(getTask("not-existing")); + t.is(error.message, "taskRepository: Unknown Task not-existing", "Correct exception"); +}); + +test("Removed task retrieval", async (t) => { + const error = await t.throwsAsync(getTask("createDebugFiles")); + t.deepEqual(error.message, + `Standard task createDebugFiles has been removed in UI5 CLI 3.0. ` + + `Please see the migration guide at https://ui5.github.io/cli/updates/migrate-v3/`, + "Correct exception"); + + const error2 = await t.throwsAsync(getTask("uglify")); + t.deepEqual(error2.message, + `Standard task uglify has been removed in UI5 CLI 3.0. ` + + `Please see the migration guide at https://ui5.github.io/cli/updates/migrate-v3/`, + "Correct exception"); + + const error3 = await t.throwsAsync(getTask("generateManifestBundle")); + t.deepEqual(error3.message, + `Standard task generateManifestBundle has been removed in UI5 CLI 3.0. ` + + `Please see the migration guide at https://ui5.github.io/cli/updates/migrate-v3/`, + "Correct exception"); +}); + +test("getRemovedTaskNames", (t) => { + t.deepEqual(getRemovedTaskNames(), ["createDebugFiles", "uglify", "generateManifestBundle"], + "Returned correct list of removed tasks"); +}); + +test("getVersions", async (t) => { + const versions = await getVersions(); + t.not(semver.valid(versions.builderVersion), null, "builder version should be set and valid"); + t.not(semver.valid(versions.fsVersion), null, "fs version should be set and valid"); +}); diff --git a/packages/builder/test/lib/tasks/transformBootstrapHtml.js b/packages/builder/test/lib/tasks/transformBootstrapHtml.js new file mode 100644 index 00000000000..60be2f402f8 --- /dev/null +++ b/packages/builder/test/lib/tasks/transformBootstrapHtml.js @@ -0,0 +1,141 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.log = { + warn: sinon.stub() + }; + + t.context.bootstrapHtmlTransformerStub = sinon.stub(); + + t.context.transformBootstrapHtml = await esmock("../../../lib/tasks/transformBootstrapHtml.js", { + "@ui5/logger": { + getLogger: sinon.stub().withArgs("builder:tasks:transformBootstrapHtml").returns(t.context.log) + }, + "../../../lib/processors/bootstrapHtmlTransformer": t.context.bootstrapHtmlTransformerStub, + }); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("Transforms index.html resource", async (t) => { + const {transformBootstrapHtml, log} = t.context; + + t.plan(5); + + const resource = {}; + + const workspace = { + byPath: (actualPath) => { + t.is(actualPath, "/resources/sap/ui/demo/app/index.html", + "Reads index.html file from application namespace."); + return Promise.resolve(resource); + }, + write: (actualResource) => { + t.deepEqual(actualResource, resource, + "Expected resource is written back to workspace"); + } + }; + + t.context.bootstrapHtmlTransformerStub.returns([resource]); + + await transformBootstrapHtml({ + workspace, + options: { + projectName: "sap.ui.demo.app", + projectNamespace: "sap/ui/demo/app" + } + }); + + t.is(t.context.bootstrapHtmlTransformerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.bootstrapHtmlTransformerStub.calledWithExactly({ + resources: [resource], + options: { + src: "resources/sap-ui-custom.js" + } + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); +}); + +test.serial("Transforms index.html resource without namespace", async (t) => { + const {transformBootstrapHtml, log} = t.context; + + t.plan(5); + + const resource = {}; + + const workspace = { + byPath: (actualPath) => { + t.is(actualPath, "/index.html", + "Reads index.html file from application namespace."); + return Promise.resolve(resource); + }, + write: (actualResource) => { + t.deepEqual(actualResource, resource, + "Expected resource is written back to workspace"); + } + }; + + t.context.bootstrapHtmlTransformerStub.returns([resource]); + + await transformBootstrapHtml({ + workspace, + options: { + projectName: "sap.ui.demo.app" + } + }); + + t.is(t.context.bootstrapHtmlTransformerStub.callCount, 1, + "Processor should be called once"); + + t.true(t.context.bootstrapHtmlTransformerStub.calledWithExactly({ + resources: [resource], + options: { + src: "resources/sap-ui-custom.js" + } + }), "Processor should be called with expected arguments"); + + t.true(log.warn.notCalled, "No warnings should be logged"); +}); + +test.serial("No index.html resource exists", async (t) => { + const {transformBootstrapHtml, log} = t.context; + + t.plan(4); + + const workspace = { + byPath: (actualPath) => { + t.is(actualPath, "/resources/sap/ui/demo/app/index.html", + "Reads index.html file from application namespace."); + return Promise.resolve(null); + }, + write: () => { + t.fail("No resources should be written to workspace"); + } + }; + + await transformBootstrapHtml({ + workspace, + options: { + projectName: "sap.ui.demo.app", + projectNamespace: "sap/ui/demo/app" + } + }); + + t.true(t.context.bootstrapHtmlTransformerStub.notCalled, + "Processor should not be called"); + + t.is(log.warn.callCount, 1, "One warning should be logged"); + t.true( + log.warn.calledWith( + `Skipping bootstrap transformation due to missing index.html in project "sap.ui.demo.app".`), + "Warning about missing index.html file should be logged"); +}); diff --git a/packages/builder/test/lib/tasks/utils/dotTheming.js b/packages/builder/test/lib/tasks/utils/dotTheming.js new file mode 100644 index 00000000000..e9e6ce64e4c --- /dev/null +++ b/packages/builder/test/lib/tasks/utils/dotTheming.js @@ -0,0 +1,186 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import esmock from "esmock"; + +test.beforeEach(async (t) => { + t.context.sinon = sinonGlobal.createSandbox(); + t.context.dotTheming = await esmock("../../../../lib/tasks/utils/dotTheming", {}); + + t.context.createDotThemingResource = (path, dotTheming) => { + return { + getPath: () => path, + getString: async () => JSON.stringify(dotTheming, null, 2), + setString: t.context.sinon.stub() + }; + }; +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test("updateLibraryDotTheming: Default case", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ] + }); + + await updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: true + }); + + t.is(resource.setString.callCount, 1); + t.deepEqual(resource.setString.getCall(0).args, [ + JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ], + sVersion: "1.2.3", + }, null, 2) + ]); +}); + +test("updateLibraryDotTheming: No themes", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ] + }); + + await updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: false + }); + + t.is(resource.setString.callCount, 1); + t.deepEqual(resource.setString.getCall(0).args, [ + JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + aFiles: [ + "existing", "entries" + ], + sVersion: "1.2.3", + bIgnore: true + }, null, 2) + ]); +}); + +test("updateLibraryDotTheming: Existing sVersion", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "1.2.3" + }); + + await updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.4", + hasThemes: true + }); + + t.is(resource.setString.callCount, 1); + t.deepEqual(resource.setString.getCall(0).args, [ + JSON.stringify({ + sEntity: "Library", + sId: "sap/ui/core", + sVersion: "1.2.4", + }, null, 2) + ]); +}); + +test("updateLibraryDotTheming: Missing sEntity", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sId: "sap/ui/core", + }); + + await t.throwsAsync(updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: true + }), { + message: "Missing 'sEntity' property in /resources/sap/ui/core/.theming" + }); +}); + +test("updateLibraryDotTheming: Incorrect sEntity", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Wrong", + sId: "sap/ui/core", + }); + + await t.throwsAsync(updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: true + }), { + message: "Incorrect 'sEntity' value 'Wrong' in /resources/sap/ui/core/.theming: Expected 'Library'" + }); +}); + +test("updateLibraryDotTheming: Missing sId", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Library", + }); + + await t.throwsAsync(updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: true + }), { + message: "Missing 'sId' property in /resources/sap/ui/core/.theming" + }); +}); + +test("updateLibraryDotTheming: Incorrect sId", async (t) => { + const {createDotThemingResource} = t.context; + const {updateLibraryDotTheming} = t.context.dotTheming; + + const resource = createDotThemingResource("/resources/sap/ui/core/.theming", { + sEntity: "Library", + sId: "Wrong", + }); + + await t.throwsAsync(updateLibraryDotTheming({ + resource, + namespace: "sap/ui/core", + version: "1.2.3", + hasThemes: true + }), { + message: "Incorrect 'sId' value 'Wrong' in /resources/sap/ui/core/.theming: Expected 'sap/ui/core'" + }); +}); diff --git a/packages/builder/test/utils/fshelper.js b/packages/builder/test/utils/fshelper.js new file mode 100644 index 00000000000..25e74974b79 --- /dev/null +++ b/packages/builder/test/utils/fshelper.js @@ -0,0 +1,24 @@ +import {readdir, readFile} from "node:fs/promises"; +import path from "node:path"; + +export async function findFiles(dirPath) { + const files = await readdir(dirPath, {withFileTypes: true, recursive: true}); + return files.filter((file) => file.isFile()).map((file) => path.join(file.parentPath || file.path, file.name)); +} + +export async function readFileContent(filePath) { + return await readFile(filePath, {encoding: "utf8"}); +} + +export async function directoryDeepEqual(t, destPath, expectedPath) { + const dest = await readdir(destPath, {recursive: true}); + const expected = await readdir(expectedPath, {recursive: true}); + t.deepEqual(dest, expected); +} + +export async function fileEqual(t, destPath, expectedPath) { + const destContent = await readFileContent(destPath); + const expectedContent = await readFileContent(expectedPath); + t.is(destContent, expectedContent); +} +