@lenne.tech/ng-base 18.1.3 → 22.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +55 -0
- package/LICENSE +2 -2
- package/README.md +237 -44
- package/fesm2022/lenne.tech-ng-base-ai.mjs +771 -0
- package/fesm2022/lenne.tech-ng-base-ai.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs +945 -0
- package/fesm2022/lenne.tech-ng-base-auth-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-auth.mjs +454 -0
- package/fesm2022/lenne.tech-ng-base-auth.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs +825 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-dialog-BRzVmKJr.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs +351 -0
- package/fesm2022/lenne.tech-ng-base-bug-bug-report-service-DWoTb_iw.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-capture-engine-TkjMDdqj.mjs +1046 -0
- package/fesm2022/lenne.tech-ng-base-bug-capture-engine-TkjMDdqj.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-constants-Brm2KGHi.mjs +4 -0
- package/fesm2022/lenne.tech-ng-base-bug-constants-Brm2KGHi.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-messages-BsP4fRQU.mjs +179 -0
- package/fesm2022/lenne.tech-ng-base-bug-messages-BsP4fRQU.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-provide.mjs +26 -0
- package/fesm2022/lenne.tech-ng-base-bug-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-report-Tzlhe1Cm.mjs +62 -0
- package/fesm2022/lenne.tech-ng-base-bug-report-Tzlhe1Cm.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug-server.mjs +818 -0
- package/fesm2022/lenne.tech-ng-base-bug-server.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-bug.mjs +196 -0
- package/fesm2022/lenne.tech-ng-base-bug.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-files-ui.mjs +445 -0
- package/fesm2022/lenne.tech-ng-base-files-ui.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-files.mjs +474 -0
- package/fesm2022/lenne.tech-ng-base-files.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-server.mjs +408 -0
- package/fesm2022/lenne.tech-ng-base-server.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-share.mjs +76 -0
- package/fesm2022/lenne.tech-ng-base-share.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-sync.mjs +1096 -0
- package/fesm2022/lenne.tech-ng-base-sync.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-testing.mjs +171 -0
- package/fesm2022/lenne.tech-ng-base-testing.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-ui-provide.mjs +124 -0
- package/fesm2022/lenne.tech-ng-base-ui-provide.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-ui.mjs +34645 -0
- package/fesm2022/lenne.tech-ng-base-ui.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base-utils.mjs +287 -0
- package/fesm2022/lenne.tech-ng-base-utils.mjs.map +1 -0
- package/fesm2022/lenne.tech-ng-base.mjs +615 -910
- package/fesm2022/lenne.tech-ng-base.mjs.map +1 -1
- package/i18n/auth/de.json +17 -0
- package/i18n/auth/en.json +17 -0
- package/i18n/bug/de.json +85 -0
- package/i18n/bug/en.json +85 -0
- package/i18n/de.json +8 -0
- package/i18n/en.json +8 -0
- package/i18n/files/de.json +27 -0
- package/i18n/files/en.json +27 -0
- package/i18n/share/de.json +8 -0
- package/i18n/share/en.json +8 -0
- package/i18n/ui/de.json +38 -0
- package/i18n/ui/en.json +38 -0
- package/package.json +127 -58
- package/types/lenne.tech-ng-base-ai.d.ts +845 -0
- package/types/lenne.tech-ng-base-auth-provide.d.ts +604 -0
- package/types/lenne.tech-ng-base-auth.d.ts +320 -0
- package/types/lenne.tech-ng-base-bug-provide.d.ts +272 -0
- package/types/lenne.tech-ng-base-bug-rolldown-runtime-C25Le4-w.d.ts +1 -0
- package/types/lenne.tech-ng-base-bug-server.d.ts +316 -0
- package/types/lenne.tech-ng-base-bug.d.ts +202 -0
- package/types/lenne.tech-ng-base-files-ui.d.ts +116 -0
- package/types/lenne.tech-ng-base-files.d.ts +288 -0
- package/types/lenne.tech-ng-base-server.d.ts +262 -0
- package/types/lenne.tech-ng-base-share.d.ts +21 -0
- package/types/lenne.tech-ng-base-sync.d.ts +380 -0
- package/types/lenne.tech-ng-base-testing.d.ts +327 -0
- package/types/lenne.tech-ng-base-ui-provide.d.ts +65 -0
- package/types/lenne.tech-ng-base-ui.d.ts +7744 -0
- package/types/lenne.tech-ng-base-utils.d.ts +150 -0
- package/types/lenne.tech-ng-base.d.ts +503 -0
- package/ui/styles/components/accordion.css +3 -0
- package/ui/styles/components/alert-dialog.css +4 -0
- package/ui/styles/components/alert.css +3 -0
- package/ui/styles/components/aspect-ratio.css +3 -0
- package/ui/styles/components/attachment.css +4 -0
- package/ui/styles/components/autocomplete.css +4 -0
- package/ui/styles/components/avatar.css +3 -0
- package/ui/styles/components/badge.css +3 -0
- package/ui/styles/components/breadcrumb.css +3 -0
- package/ui/styles/components/bubble.css +3 -0
- package/ui/styles/components/button-group.css +3 -0
- package/ui/styles/components/button.css +3 -0
- package/ui/styles/components/calendar.css +5 -0
- package/ui/styles/components/card.css +3 -0
- package/ui/styles/components/checkbox.css +3 -0
- package/ui/styles/components/code-block.css +4 -0
- package/ui/styles/components/collapsible.css +3 -0
- package/ui/styles/components/combobox.css +5 -0
- package/ui/styles/components/command.css +5 -0
- package/ui/styles/components/context-menu.css +3 -0
- package/ui/styles/components/date-picker.css +7 -0
- package/ui/styles/components/dialog.css +4 -0
- package/ui/styles/components/drawer.css +3 -0
- package/ui/styles/components/dropdown-menu.css +3 -0
- package/ui/styles/components/empty.css +3 -0
- package/ui/styles/components/field.css +5 -0
- package/ui/styles/components/hover-card.css +3 -0
- package/ui/styles/components/index.css +91 -0
- package/ui/styles/components/input-group.css +6 -0
- package/ui/styles/components/input-otp.css +3 -0
- package/ui/styles/components/input.css +3 -0
- package/ui/styles/components/item.css +4 -0
- package/ui/styles/components/kbd.css +3 -0
- package/ui/styles/components/label.css +3 -0
- package/ui/styles/components/lt-breadcrumb.css +3 -0
- package/ui/styles/components/lt-bug-attachments.css +5 -0
- package/ui/styles/components/lt-bug-journey-timeline.css +5 -0
- package/ui/styles/components/lt-bug-report-dialog.css +15 -0
- package/ui/styles/components/lt-bug-report.css +4 -0
- package/ui/styles/components/lt-color-mode-button.css +3 -0
- package/ui/styles/components/lt-confirm-dialog.css +4 -0
- package/ui/styles/components/lt-data-view.css +5 -0
- package/ui/styles/components/lt-date-picker.css +3 -0
- package/ui/styles/components/lt-date-time-picker.css +5 -0
- package/ui/styles/components/lt-enter-action.css +2 -0
- package/ui/styles/components/lt-footer.css +3 -0
- package/ui/styles/components/lt-form-field.css +4 -0
- package/ui/styles/components/lt-header.css +5 -0
- package/ui/styles/components/lt-imports.css +21 -0
- package/ui/styles/components/lt-navigation-menu.css +3 -0
- package/ui/styles/components/lt-notice.css +3 -0
- package/ui/styles/components/lt-overlay-defaults.css +3 -0
- package/ui/styles/components/lt-page-card.css +3 -0
- package/ui/styles/components/lt-page-title.css +2 -0
- package/ui/styles/components/lt-password-input.css +4 -0
- package/ui/styles/components/lt-readonly-field.css +2 -0
- package/ui/styles/components/lt-select.css +4 -0
- package/ui/styles/components/lt-text-field.css +5 -0
- package/ui/styles/components/lt-toolbar-item.css +2 -0
- package/ui/styles/components/lt-toolbar.css +3 -0
- package/ui/styles/components/lt-tus-upload-component.css +5 -0
- package/ui/styles/components/marker.css +3 -0
- package/ui/styles/components/menubar.css +3 -0
- package/ui/styles/components/message-scroller.css +4 -0
- package/ui/styles/components/message.css +3 -0
- package/ui/styles/components/native-select.css +3 -0
- package/ui/styles/components/navigation-menu.css +3 -0
- package/ui/styles/components/pagination.css +4 -0
- package/ui/styles/components/popover.css +3 -0
- package/ui/styles/components/progress.css +3 -0
- package/ui/styles/components/questionnaire.css +4 -0
- package/ui/styles/components/radio-group.css +3 -0
- package/ui/styles/components/resizable.css +3 -0
- package/ui/styles/components/select.css +3 -0
- package/ui/styles/components/separator.css +3 -0
- package/ui/styles/components/sheet.css +4 -0
- package/ui/styles/components/sidebar.css +9 -0
- package/ui/styles/components/skeleton.css +3 -0
- package/ui/styles/components/slider.css +3 -0
- package/ui/styles/components/sonner.css +4 -0
- package/ui/styles/components/spinner.css +3 -0
- package/ui/styles/components/switch.css +3 -0
- package/ui/styles/components/table.css +3 -0
- package/ui/styles/components/tabs.css +4 -0
- package/ui/styles/components/textarea.css +3 -0
- package/ui/styles/components/toggle-group.css +4 -0
- package/ui/styles/components/toggle.css +3 -0
- package/ui/styles/components/tooltip.css +3 -0
- package/ui/styles/components/trace.css +3 -0
- package/ui/styles/components/typography.css +3 -0
- package/ui/styles/components/utils.css +2 -0
- package/ui/styles/core.css +137 -0
- package/ui/styles/palette.css +94 -0
- package/ui/styles/theme.css +14 -0
- package/ui/styles/transitions.css +131 -0
- package/base-cms/index.d.ts +0 -5
- package/base-cms/interfaces/base-cms-module-config.interface.d.ts +0 -16
- package/base-cms/pages/base-cms/base-cms.component.d.ts +0 -92
- package/base-cms/public-api.d.ts +0 -5
- package/base-components/components/breadcrumb/breadcrumb.component.d.ts +0 -10
- package/base-components/components/change-password/change-password.component.d.ts +0 -24
- package/base-components/components/checkbox/checkbox.component.d.ts +0 -12
- package/base-components/components/delete-account/delete-account.component.d.ts +0 -15
- package/base-components/components/fab-button/fab-button.component.d.ts +0 -16
- package/base-components/components/input/input.component.d.ts +0 -27
- package/base-components/components/loading-bar/loading-bar.component.d.ts +0 -17
- package/base-components/components/model-form/model-form-base.component.d.ts +0 -64
- package/base-components/components/model-form/model-form.component.d.ts +0 -139
- package/base-components/components/model-form-sub/model-form-sub.component.d.ts +0 -56
- package/base-components/components/model-table/model-table.component.d.ts +0 -115
- package/base-components/components/refence-input/refence-input.component.d.ts +0 -84
- package/base-components/components/select/select.component.d.ts +0 -25
- package/base-components/components/skeleton-loader/skeleton-loader.component.d.ts +0 -10
- package/base-components/components/tags/tags.component.d.ts +0 -60
- package/base-components/components/textarea/textarea.component.d.ts +0 -21
- package/base-components/components/toast/toast.component.d.ts +0 -19
- package/base-components/components/upload-image/upload-image.component.d.ts +0 -111
- package/base-components/directives/back-button.directive.d.ts +0 -9
- package/base-components/directives/context-menu.directive.d.ts +0 -22
- package/base-components/directives/sort.directive.d.ts +0 -15
- package/base-components/directives/stop-propagation.directive.d.ts +0 -10
- package/base-components/index.d.ts +0 -5
- package/base-components/pipes/ellipses.pipe.d.ts +0 -10
- package/base-components/public-api.d.ts +0 -26
- package/base-prototype/index.d.ts +0 -5
- package/base-prototype/pages/email-verification/email-verification.component.d.ts +0 -18
- package/base-prototype/pages/forgot-password/forgot-password.component.d.ts +0 -26
- package/base-prototype/pages/login/login.component.d.ts +0 -33
- package/base-prototype/pages/profile/profile.component.d.ts +0 -24
- package/base-prototype/pages/register/register.component.d.ts +0 -31
- package/base-prototype/pages/set-password/set-password.component.d.ts +0 -29
- package/base-prototype/public-api.d.ts +0 -9
- package/esm2022/base-cms/interfaces/base-cms-module-config.interface.mjs +0 -3
- package/esm2022/base-cms/lenne.tech-ng-base-base-cms.mjs +0 -5
- package/esm2022/base-cms/pages/base-cms/base-cms.component.mjs +0 -238
- package/esm2022/base-cms/public-api.mjs +0 -8
- package/esm2022/base-components/components/breadcrumb/breadcrumb.component.mjs +0 -23
- package/esm2022/base-components/components/change-password/change-password.component.mjs +0 -97
- package/esm2022/base-components/components/checkbox/checkbox.component.mjs +0 -38
- package/esm2022/base-components/components/delete-account/delete-account.component.mjs +0 -59
- package/esm2022/base-components/components/fab-button/fab-button.component.mjs +0 -24
- package/esm2022/base-components/components/input/input.component.mjs +0 -75
- package/esm2022/base-components/components/loading-bar/loading-bar.component.mjs +0 -37
- package/esm2022/base-components/components/model-form/model-form-base.component.mjs +0 -145
- package/esm2022/base-components/components/model-form/model-form.component.mjs +0 -591
- package/esm2022/base-components/components/model-form-sub/model-form-sub.component.mjs +0 -117
- package/esm2022/base-components/components/model-table/model-table.component.mjs +0 -478
- package/esm2022/base-components/components/refence-input/refence-input.component.mjs +0 -278
- package/esm2022/base-components/components/select/select.component.mjs +0 -62
- package/esm2022/base-components/components/skeleton-loader/skeleton-loader.component.mjs +0 -33
- package/esm2022/base-components/components/tags/tags.component.mjs +0 -227
- package/esm2022/base-components/components/textarea/textarea.component.mjs +0 -79
- package/esm2022/base-components/components/toast/toast.component.mjs +0 -69
- package/esm2022/base-components/components/upload-image/upload-image.component.mjs +0 -346
- package/esm2022/base-components/directives/back-button.directive.mjs +0 -25
- package/esm2022/base-components/directives/context-menu.directive.mjs +0 -112
- package/esm2022/base-components/directives/sort.directive.mjs +0 -61
- package/esm2022/base-components/directives/stop-propagation.directive.mjs +0 -24
- package/esm2022/base-components/lenne.tech-ng-base-base-components.mjs +0 -5
- package/esm2022/base-components/pipes/ellipses.pipe.mjs +0 -23
- package/esm2022/base-components/public-api.mjs +0 -30
- package/esm2022/base-prototype/lenne.tech-ng-base-base-prototype.mjs +0 -5
- package/esm2022/base-prototype/pages/email-verification/email-verification.component.mjs +0 -60
- package/esm2022/base-prototype/pages/forgot-password/forgot-password.component.mjs +0 -78
- package/esm2022/base-prototype/pages/login/login.component.mjs +0 -114
- package/esm2022/base-prototype/pages/profile/profile.component.mjs +0 -87
- package/esm2022/base-prototype/pages/register/register.component.mjs +0 -100
- package/esm2022/base-prototype/pages/set-password/set-password.component.mjs +0 -91
- package/esm2022/base-prototype/public-api.mjs +0 -11
- package/esm2022/lenne.tech-ng-base.mjs +0 -5
- package/esm2022/public-api.mjs +0 -25
- package/esm2022/shared/classes/auth.class.mjs +0 -14
- package/esm2022/shared/classes/basic-user.class.mjs +0 -49
- package/esm2022/shared/classes/graphql-enum.class.mjs +0 -10
- package/esm2022/shared/classes/graphql-meta.class.mjs +0 -345
- package/esm2022/shared/classes/graphql-type.class.mjs +0 -14
- package/esm2022/shared/classes/helper.class.mjs +0 -307
- package/esm2022/shared/classes/sort.class.mjs +0 -38
- package/esm2022/shared/classes/standard.class.mjs +0 -40
- package/esm2022/shared/decorators/debounce.decorator.mjs +0 -12
- package/esm2022/shared/enums/comparison-operator.enum.mjs +0 -16
- package/esm2022/shared/enums/graphql-request-type.enum.mjs +0 -10
- package/esm2022/shared/enums/logical-operator.enum.mjs +0 -10
- package/esm2022/shared/enums/skeleton-type.enum.mjs +0 -7
- package/esm2022/shared/enums/sort-order.enum.mjs +0 -6
- package/esm2022/shared/enums/toast-type.enum.mjs +0 -8
- package/esm2022/shared/functions/ws-client.function.mjs +0 -39
- package/esm2022/shared/interfaces/base-module-config.interface.mjs +0 -3
- package/esm2022/shared/interfaces/breadcrumb.interface.mjs +0 -2
- package/esm2022/shared/interfaces/can-component-deactivate.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-enum-values.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-field-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-model-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cms-table-field.interface.mjs +0 -2
- package/esm2022/shared/interfaces/combined-filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/compress-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/cropping-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/field-object.interface.mjs +0 -2
- package/esm2022/shared/interfaces/field.interface.mjs +0 -2
- package/esm2022/shared/interfaces/filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/find-args.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-crud-type.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-fields-object.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-options.interface.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-plus-options.interfacen.mjs +0 -2
- package/esm2022/shared/interfaces/graphql-type-collection.interface.mjs +0 -2
- package/esm2022/shared/interfaces/login-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/register-config.interface.mjs +0 -2
- package/esm2022/shared/interfaces/single-filter-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/sort-input.interface.mjs +0 -2
- package/esm2022/shared/interfaces/time-components.interface.mjs +0 -2
- package/esm2022/shared/interfaces/toast.interface.mjs +0 -2
- package/esm2022/shared/lenne.tech-ng-base-shared.mjs +0 -5
- package/esm2022/shared/public-api.mjs +0 -74
- package/esm2022/shared/services/auth.service.mjs +0 -152
- package/esm2022/shared/services/breadcrumb.service.mjs +0 -69
- package/esm2022/shared/services/cms.service.mjs +0 -82
- package/esm2022/shared/services/file.service.mjs +0 -86
- package/esm2022/shared/services/forms.service.mjs +0 -61
- package/esm2022/shared/services/graphql-meta.service.mjs +0 -132
- package/esm2022/shared/services/graphql-plus.service.mjs +0 -93
- package/esm2022/shared/services/graphql.service.mjs +0 -510
- package/esm2022/shared/services/image.service.mjs +0 -102
- package/esm2022/shared/services/loader.service.mjs +0 -78
- package/esm2022/shared/services/navigation.service.mjs +0 -38
- package/esm2022/shared/services/scroll.service.mjs +0 -207
- package/esm2022/shared/services/storage.service.mjs +0 -149
- package/esm2022/shared/services/toast.service.mjs +0 -39
- package/esm2022/shared/services/user.service.mjs +0 -160
- package/esm2022/shared/services/ws.service.mjs +0 -17
- package/esm2022/shared/types/graphql-fields.type.mjs +0 -2
- package/esm2022/shared/types/storage.type.mjs +0 -2
- package/esm2022/shared/validators/email.validator.mjs +0 -7
- package/esm2022/shared/validators/match.validator.mjs +0 -22
- package/esm2022/shared/validators/secure-password.validator.mjs +0 -7
- package/esm2022/src/base.module.mjs +0 -64
- package/esm2022/src/decorators/auto-unsubscribe.decorator.mjs +0 -23
- package/esm2022/src/directives/lazy-load.directive.mjs +0 -48
- package/esm2022/src/directives/match-height.directive.mjs +0 -92
- package/esm2022/src/directives/resizable.directive.mjs +0 -92
- package/esm2022/src/factories/apollo-options.factory.mjs +0 -108
- package/esm2022/src/functions/extract-files.function.mjs +0 -87
- package/esm2022/src/guards/auth.guard.mjs +0 -91
- package/esm2022/src/guards/can-deactivate.guard.mjs +0 -16
- package/esm2022/src/guards/logout.guard.mjs +0 -34
- package/esm2022/src/pipes/date-ago.pipe.mjs +0 -64
- package/esm2022/src/pipes/safe-html.pipe.mjs +0 -22
- package/esm2022/src/services/fullscreen.service.mjs +0 -51
- package/esm2022/src/services/seo.service.mjs +0 -117
- package/esm2022/src/services/theme.service.mjs +0 -44
- package/fesm2022/lenne.tech-ng-base-base-cms.mjs +0 -251
- package/fesm2022/lenne.tech-ng-base-base-cms.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-base-components.mjs +0 -2917
- package/fesm2022/lenne.tech-ng-base-base-components.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-base-prototype.mjs +0 -505
- package/fesm2022/lenne.tech-ng-base-base-prototype.mjs.map +0 -1
- package/fesm2022/lenne.tech-ng-base-shared.mjs +0 -2863
- package/fesm2022/lenne.tech-ng-base-shared.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/public-api.d.ts +0 -17
- package/shared/classes/auth.class.d.ts +0 -10
- package/shared/classes/basic-user.class.d.ts +0 -27
- package/shared/classes/graphql-enum.class.d.ts +0 -7
- package/shared/classes/graphql-meta.class.d.ts +0 -66
- package/shared/classes/graphql-type.class.d.ts +0 -13
- package/shared/classes/helper.class.d.ts +0 -99
- package/shared/classes/sort.class.d.ts +0 -12
- package/shared/classes/standard.class.d.ts +0 -32
- package/shared/decorators/debounce.decorator.d.ts +0 -1
- package/shared/enums/comparison-operator.enum.d.ts +0 -14
- package/shared/enums/graphql-request-type.enum.d.ts +0 -8
- package/shared/enums/logical-operator.enum.d.ts +0 -8
- package/shared/enums/skeleton-type.enum.d.ts +0 -5
- package/shared/enums/sort-order.enum.d.ts +0 -4
- package/shared/enums/toast-type.enum.d.ts +0 -6
- package/shared/functions/ws-client.function.d.ts +0 -5
- package/shared/index.d.ts +0 -5
- package/shared/interfaces/base-module-config.interface.d.ts +0 -61
- package/shared/interfaces/breadcrumb.interface.d.ts +0 -4
- package/shared/interfaces/can-component-deactivate.interface.d.ts +0 -7
- package/shared/interfaces/cms-enum-values.interface.d.ts +0 -4
- package/shared/interfaces/cms-field-config.interface.d.ts +0 -45
- package/shared/interfaces/cms-model-config.interface.d.ts +0 -14
- package/shared/interfaces/cms-table-field.interface.d.ts +0 -10
- package/shared/interfaces/combined-filter-input.interface.d.ts +0 -9
- package/shared/interfaces/compress-options.interface.d.ts +0 -15
- package/shared/interfaces/cropping-options.interface.d.ts +0 -12
- package/shared/interfaces/field-object.interface.d.ts +0 -6
- package/shared/interfaces/field.interface.d.ts +0 -5
- package/shared/interfaces/filter-input.interface.d.ts +0 -9
- package/shared/interfaces/find-args.interface.d.ts +0 -11
- package/shared/interfaces/graphql-crud-type.interface.d.ts +0 -7
- package/shared/interfaces/graphql-fields-object.interface.d.ts +0 -6
- package/shared/interfaces/graphql-options.interface.d.ts +0 -11
- package/shared/interfaces/graphql-plus-options.interfacen.d.ts +0 -9
- package/shared/interfaces/graphql-type-collection.interface.d.ts +0 -7
- package/shared/interfaces/login-config.interface.d.ts +0 -6
- package/shared/interfaces/register-config.interface.d.ts +0 -4
- package/shared/interfaces/single-filter-input.interface.d.ts +0 -11
- package/shared/interfaces/sort-input.interface.d.ts +0 -5
- package/shared/interfaces/time-components.interface.d.ts +0 -6
- package/shared/interfaces/toast.interface.d.ts +0 -8
- package/shared/public-api.d.ts +0 -65
- package/shared/services/auth.service.d.ts +0 -44
- package/shared/services/breadcrumb.service.d.ts +0 -32
- package/shared/services/cms.service.d.ts +0 -27
- package/shared/services/file.service.d.ts +0 -22
- package/shared/services/forms.service.d.ts +0 -14
- package/shared/services/graphql-meta.service.d.ts +0 -45
- package/shared/services/graphql-plus.service.d.ts +0 -27
- package/shared/services/graphql.service.d.ts +0 -71
- package/shared/services/image.service.d.ts +0 -27
- package/shared/services/loader.service.d.ts +0 -32
- package/shared/services/navigation.service.d.ts +0 -15
- package/shared/services/scroll.service.d.ts +0 -85
- package/shared/services/storage.service.d.ts +0 -49
- package/shared/services/toast.service.d.ts +0 -15
- package/shared/services/user.service.d.ts +0 -77
- package/shared/services/ws.service.d.ts +0 -8
- package/shared/types/graphql-fields.type.d.ts +0 -5
- package/shared/types/storage.type.d.ts +0 -4
- package/shared/validators/email.validator.d.ts +0 -1
- package/shared/validators/match.validator.d.ts +0 -4
- package/shared/validators/secure-password.validator.d.ts +0 -1
- package/src/base.module.d.ts +0 -16
- package/src/decorators/auto-unsubscribe.decorator.d.ts +0 -5
- package/src/directives/lazy-load.directive.d.ts +0 -17
- package/src/directives/match-height.directive.d.ts +0 -20
- package/src/directives/resizable.directive.d.ts +0 -26
- package/src/factories/apollo-options.factory.d.ts +0 -16
- package/src/functions/extract-files.function.d.ts +0 -15
- package/src/guards/auth.guard.d.ts +0 -27
- package/src/guards/can-deactivate.guard.d.ts +0 -10
- package/src/guards/logout.guard.d.ts +0 -16
- package/src/pipes/date-ago.pipe.d.ts +0 -34
- package/src/pipes/safe-html.pipe.d.ts +0 -13
- package/src/services/fullscreen.service.d.ts +0 -18
- package/src/services/seo.service.d.ts +0 -61
- package/src/services/theme.service.d.ts +0 -17
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
const LT_LINEAR_API_URL = "https://api.linear.app/graphql";
|
|
2
|
+
const LT_BUG_LABELS = Object.freeze({
|
|
3
|
+
bug: {
|
|
4
|
+
color: "#f87171",
|
|
5
|
+
name: "bug"
|
|
6
|
+
},
|
|
7
|
+
enhancement: {
|
|
8
|
+
color: "#34d399",
|
|
9
|
+
name: "enhancement"
|
|
10
|
+
},
|
|
11
|
+
feature: {
|
|
12
|
+
color: "#60a5fa",
|
|
13
|
+
name: "feature"
|
|
14
|
+
},
|
|
15
|
+
other: {
|
|
16
|
+
color: "#a78bfa",
|
|
17
|
+
name: "other"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
var LtLinearError = class extends Error {
|
|
21
|
+
status;
|
|
22
|
+
constructor(message, status) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.name = "LtLinearError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const MAX_PAGES = 10;
|
|
29
|
+
var LtLinearClient = class {
|
|
30
|
+
options;
|
|
31
|
+
apiUrl;
|
|
32
|
+
fetchFn;
|
|
33
|
+
timeoutMs;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.apiUrl = options.apiUrl ?? "https://api.linear.app/graphql";
|
|
37
|
+
this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
38
|
+
this.timeoutMs = options.timeoutMs ?? 15e3;
|
|
39
|
+
}
|
|
40
|
+
async request(query, variables = {}) {
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await this.fetchFn(this.apiUrl, {
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
query,
|
|
46
|
+
variables
|
|
47
|
+
}),
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: this.options.apiKey,
|
|
50
|
+
"Content-Type": "application/json"
|
|
51
|
+
},
|
|
52
|
+
method: "POST",
|
|
53
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new LtLinearError(`Linear API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
57
|
+
}
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const text = await response.text().catch(() => "");
|
|
60
|
+
throw new LtLinearError(`Linear API request failed: ${response.status} ${response.statusText} - ${text.slice(0, 500)}`, response.status);
|
|
61
|
+
}
|
|
62
|
+
const result = await response.json().catch(() => null);
|
|
63
|
+
if (!result) throw new LtLinearError("Linear API answered with invalid JSON");
|
|
64
|
+
if (result.errors?.length) throw new LtLinearError(`Linear GraphQL error: ${JSON.stringify(result.errors).slice(0, 1e3)}`);
|
|
65
|
+
return result.data;
|
|
66
|
+
}
|
|
67
|
+
async getTeams() {
|
|
68
|
+
return this.collect("query Teams($after: String) { teams(first: 250, after: $after) { nodes { id name key } pageInfo { hasNextPage endCursor } } }", (data) => data.teams);
|
|
69
|
+
}
|
|
70
|
+
async findTeam(nameOrKey) {
|
|
71
|
+
const wanted = nameOrKey.trim().toLowerCase();
|
|
72
|
+
return (await this.getTeams()).find((team) => team.name.toLowerCase() === wanted || team.key.toLowerCase() === wanted) ?? null;
|
|
73
|
+
}
|
|
74
|
+
async findProject(name) {
|
|
75
|
+
const wanted = name.trim().toLowerCase();
|
|
76
|
+
return (await this.collect("query Projects($after: String) { projects(first: 250, after: $after) { nodes { id name } pageInfo { hasNextPage endCursor } } }", (data) => data.projects)).find((project) => project.name.toLowerCase() === wanted) ?? null;
|
|
77
|
+
}
|
|
78
|
+
async findLabel(teamId, name) {
|
|
79
|
+
const wanted = name.toLowerCase();
|
|
80
|
+
const matches = (await this.collect("query Labels($after: String) { issueLabels(first: 250, after: $after) { nodes { id name team { id } } pageInfo { hasNextPage endCursor } } }", (data) => data.issueLabels)).filter((label) => label.name.toLowerCase() === wanted && (!label.team || label.team.id === teamId));
|
|
81
|
+
return (matches.find((label) => label.team?.id === teamId) ?? matches[0])?.id ?? null;
|
|
82
|
+
}
|
|
83
|
+
async createLabel(teamId, name, color) {
|
|
84
|
+
const data = await this.request("mutation CreateLabel($input: IssueLabelCreateInput!) { issueLabelCreate(input: $input) { success issueLabel { id name } } }", { input: {
|
|
85
|
+
color,
|
|
86
|
+
name,
|
|
87
|
+
teamId
|
|
88
|
+
} });
|
|
89
|
+
return data.issueLabelCreate?.success ? data.issueLabelCreate.issueLabel?.id ?? null : null;
|
|
90
|
+
}
|
|
91
|
+
async createIssue(input) {
|
|
92
|
+
const payload = {
|
|
93
|
+
description: input.description,
|
|
94
|
+
teamId: input.teamId,
|
|
95
|
+
title: input.title
|
|
96
|
+
};
|
|
97
|
+
if (input.projectId) payload["projectId"] = input.projectId;
|
|
98
|
+
if (input.labelIds?.length) payload["labelIds"] = input.labelIds;
|
|
99
|
+
const data = await this.request("mutation CreateIssue($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id url identifier title } } }", { input: payload });
|
|
100
|
+
if (!data.issueCreate?.success || !data.issueCreate.issue?.id) throw new LtLinearError("Linear did not create the issue");
|
|
101
|
+
return data.issueCreate.issue;
|
|
102
|
+
}
|
|
103
|
+
async uploadFile(content, filename, contentType) {
|
|
104
|
+
if (!content.length) throw new LtLinearError(`File ${filename} has zero size`);
|
|
105
|
+
const data = await this.request("mutation FileUpload($contentType: String!, $filename: String!, $size: Int!) { fileUpload(contentType: $contentType, filename: $filename, size: $size) { success uploadFile { uploadUrl assetUrl headers { key value } } } }", {
|
|
106
|
+
contentType,
|
|
107
|
+
filename,
|
|
108
|
+
size: content.length
|
|
109
|
+
});
|
|
110
|
+
const upload = data.fileUpload?.uploadFile;
|
|
111
|
+
if (!data.fileUpload?.success || !upload?.uploadUrl || !upload.assetUrl) throw new LtLinearError("Failed to get upload URL from Linear");
|
|
112
|
+
if (!/^https:\/\//i.test(upload.uploadUrl)) throw new LtLinearError("Linear returned a non-HTTPS upload URL");
|
|
113
|
+
const headers = new Headers({ "Content-Type": contentType });
|
|
114
|
+
for (const { key, value } of upload.headers ?? []) headers.set(key, value);
|
|
115
|
+
let response;
|
|
116
|
+
try {
|
|
117
|
+
const body = new Uint8Array(content.byteLength);
|
|
118
|
+
body.set(content);
|
|
119
|
+
response = await this.fetchFn(upload.uploadUrl, {
|
|
120
|
+
body,
|
|
121
|
+
headers,
|
|
122
|
+
method: "PUT",
|
|
123
|
+
signal: AbortSignal.timeout(this.timeoutMs * 4)
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new LtLinearError(`Failed to upload file: ${error instanceof Error ? error.message : String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) throw new LtLinearError(`Failed to upload file: ${response.status} ${response.statusText}`, response.status);
|
|
129
|
+
return upload.assetUrl;
|
|
130
|
+
}
|
|
131
|
+
async createAttachment(issueId, url, title) {
|
|
132
|
+
const data = await this.request("mutation CreateAttachment($input: AttachmentCreateInput!) { attachmentCreate(input: $input) { success attachment { id } } }", { input: {
|
|
133
|
+
issueId,
|
|
134
|
+
title,
|
|
135
|
+
url
|
|
136
|
+
} });
|
|
137
|
+
return data.attachmentCreate?.success ? data.attachmentCreate.attachment?.id ?? null : null;
|
|
138
|
+
}
|
|
139
|
+
async collect(query, pick) {
|
|
140
|
+
const items = [];
|
|
141
|
+
let after = null;
|
|
142
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
143
|
+
const connection = pick(await this.request(query, { after }));
|
|
144
|
+
items.push(...connection?.nodes ?? []);
|
|
145
|
+
if (!connection?.pageInfo?.hasNextPage || !connection.pageInfo.endCursor) break;
|
|
146
|
+
after = connection.pageInfo.endCursor;
|
|
147
|
+
}
|
|
148
|
+
return items;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function formatLtBugReportForLinear(report, options = {}) {
|
|
152
|
+
const timeZone = options.timeZone ?? "Europe/Berlin";
|
|
153
|
+
const sections = [];
|
|
154
|
+
const reportParts = [`**Beschreibung:**\n${report.description}`];
|
|
155
|
+
if (report.expectedBehavior) reportParts.push(`**Erwartetes Verhalten:**\n${report.expectedBehavior}`);
|
|
156
|
+
if (report.stepsToReproduce) reportParts.push(`**Schritte zur Reproduktion:**\n${report.stepsToReproduce}`);
|
|
157
|
+
sections.push(`## Bug Report\n\n${reportParts.join("\n\n")}`);
|
|
158
|
+
const info = report.browserInfo;
|
|
159
|
+
if (info) {
|
|
160
|
+
const browser = `${info.browser?.name || "Unknown"} ${info.browser?.version || ""}`.trim();
|
|
161
|
+
const os = `${info.os?.name || info.platform || "Unknown"} ${info.os?.version || ""}`.trim();
|
|
162
|
+
sections.push([
|
|
163
|
+
"---",
|
|
164
|
+
"",
|
|
165
|
+
"## Kontext",
|
|
166
|
+
"",
|
|
167
|
+
"| | |",
|
|
168
|
+
"|---|---|",
|
|
169
|
+
`| **URL** | ${cell(info.url ?? "")} |`,
|
|
170
|
+
`| **Browser** | ${cell(browser)} |`,
|
|
171
|
+
`| **OS** | ${cell(os)} |`,
|
|
172
|
+
`| **Viewport** | ${info.viewport ? `${info.viewport.width}x${info.viewport.height}` : ""} |`,
|
|
173
|
+
`| **Zeitpunkt** | ${cell(info.timestamp ?? "")} |`
|
|
174
|
+
].join("\n"));
|
|
175
|
+
}
|
|
176
|
+
if (report.userInteractions.length) {
|
|
177
|
+
const rows = report.userInteractions.map((event) => `| ${time(event.timestamp, timeZone)} | ${cell(eventType(event.type))} | ${cell(shorten(event.target, 40))} | ${cell(eventDetails(event))} |`);
|
|
178
|
+
sections.push([
|
|
179
|
+
"---",
|
|
180
|
+
"",
|
|
181
|
+
"## User Journey",
|
|
182
|
+
"",
|
|
183
|
+
`> Letzte ${report.userInteractions.length} Aktionen vor dem Bug`,
|
|
184
|
+
"",
|
|
185
|
+
"| Zeit | Aktion | Element | Details |",
|
|
186
|
+
"|------|--------|---------|---------|",
|
|
187
|
+
...rows
|
|
188
|
+
].join("\n"));
|
|
189
|
+
}
|
|
190
|
+
const technical = [];
|
|
191
|
+
if (report.consoleLogs.length) {
|
|
192
|
+
const logs = report.consoleLogs.slice(-10);
|
|
193
|
+
const lines = logs.map((log) => `[${log.timestamp}] ${log.level.toUpperCase()}: ${log.message.join(" ")}`.replace(/```/g, "```"));
|
|
194
|
+
technical.push(`### Console Logs (${logs.length})\n\n\`\`\`\n${lines.join("\n")}\n\`\`\``);
|
|
195
|
+
}
|
|
196
|
+
if (report.networkRequests.length) {
|
|
197
|
+
const requests = report.networkRequests.slice(-10);
|
|
198
|
+
const failed = requests.filter((request) => request.status >= 400 || request.status === 0);
|
|
199
|
+
let content = [
|
|
200
|
+
"| Method | URL | Status |",
|
|
201
|
+
"|--------|-----|--------|",
|
|
202
|
+
...requests.map((request) => `| ${cell(request.method)} | ${cell(shorten(request.url, 60))} | ${request.status === 0 ? "ERR" : request.status} |`)
|
|
203
|
+
].join("\n");
|
|
204
|
+
if (failed.length) {
|
|
205
|
+
const lines = failed.map((request) => {
|
|
206
|
+
const reason = request.status === 0 ? request.error || request.statusText : `${request.status} ${request.statusText}`.trim();
|
|
207
|
+
const body = request.responseBody ? `\n \`${shorten(request.responseBody, 200).replace(/`/g, "'").replace(/\s+/g, " ")}\`` : "";
|
|
208
|
+
return `- ${request.method} ${request.url.slice(0, 60)} → ${reason}${body}`;
|
|
209
|
+
});
|
|
210
|
+
content += `\n\n**Fehlerhafte Requests:**\n${lines.join("\n")}`;
|
|
211
|
+
}
|
|
212
|
+
technical.push(`### Network Requests (${requests.length})\n\n${content}`);
|
|
213
|
+
}
|
|
214
|
+
if (technical.length) sections.push(`---\n\n## Technische Details\n\n${technical.join("\n\n")}`);
|
|
215
|
+
const notes = [];
|
|
216
|
+
if (report.attachments.length) {
|
|
217
|
+
const screenshots = report.attachments.filter((file) => file.isScreenshot).length;
|
|
218
|
+
const others = report.attachments.length - screenshots;
|
|
219
|
+
notes.push(screenshots && others ? `*${screenshots} Screenshot(s) und ${others} weitere Anhänge*` : screenshots ? `*${screenshots} Screenshot(s) angehängt*` : `*${others} Anhänge*`);
|
|
220
|
+
}
|
|
221
|
+
if (report.skippedAttachments) notes.push(`*${report.skippedAttachments} Anhang/Anhänge nicht übernommen (zu groß oder zu viele)*`);
|
|
222
|
+
if (notes.length) sections.push(`---\n\n${notes.join("\n\n")}`);
|
|
223
|
+
return {
|
|
224
|
+
description: sections.join("\n\n"),
|
|
225
|
+
title: `${report.type.toUpperCase()}: ${report.title}`
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function cell(value) {
|
|
229
|
+
return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*[\r\n]+\s*/g, " ");
|
|
230
|
+
}
|
|
231
|
+
function shorten(value, max) {
|
|
232
|
+
return value.length > max ? `${value.slice(0, max - 3)}...` : value;
|
|
233
|
+
}
|
|
234
|
+
function eventType(type) {
|
|
235
|
+
return type.replace(/_/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
236
|
+
}
|
|
237
|
+
function time(timestamp, timeZone) {
|
|
238
|
+
const date = new Date(timestamp);
|
|
239
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
240
|
+
try {
|
|
241
|
+
return date.toLocaleTimeString("de-DE", {
|
|
242
|
+
hour: "2-digit",
|
|
243
|
+
minute: "2-digit",
|
|
244
|
+
second: "2-digit",
|
|
245
|
+
timeZone
|
|
246
|
+
});
|
|
247
|
+
} catch {
|
|
248
|
+
return date.toISOString().slice(11, 19);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function eventDetails(event) {
|
|
252
|
+
const metadata = event.metadata;
|
|
253
|
+
if (!metadata) return "";
|
|
254
|
+
if (metadata.toUrl) return `→ ${metadata.toUrl.slice(0, 50)}`;
|
|
255
|
+
if (metadata.text) return metadata.text.slice(0, 40);
|
|
256
|
+
if (metadata.errorMessage) return `Error: ${metadata.errorMessage.slice(0, 40)}`;
|
|
257
|
+
if (metadata.key) return `Key: ${metadata.key}`;
|
|
258
|
+
return "";
|
|
259
|
+
}
|
|
260
|
+
const LT_BUG_REDACTED = "[redacted]";
|
|
261
|
+
const LT_BUG_SENSITIVE_HEADERS = Object.freeze([
|
|
262
|
+
"api-key",
|
|
263
|
+
"authorization",
|
|
264
|
+
"cookie",
|
|
265
|
+
"proxy-authorization",
|
|
266
|
+
"set-cookie",
|
|
267
|
+
"x-access-token",
|
|
268
|
+
"x-api-key",
|
|
269
|
+
"x-auth-token",
|
|
270
|
+
"x-csrf-token",
|
|
271
|
+
"x-system-setup-token",
|
|
272
|
+
"x-xsrf-token"
|
|
273
|
+
]);
|
|
274
|
+
const LT_BUG_BODY_EXCLUDED_SEGMENTS = Object.freeze([
|
|
275
|
+
"auth",
|
|
276
|
+
"iam",
|
|
277
|
+
"login",
|
|
278
|
+
"logout",
|
|
279
|
+
"oauth",
|
|
280
|
+
"oauth2",
|
|
281
|
+
"passkey",
|
|
282
|
+
"reset-password",
|
|
283
|
+
"sign-in",
|
|
284
|
+
"sign-up",
|
|
285
|
+
"signin",
|
|
286
|
+
"signup",
|
|
287
|
+
"system-setup",
|
|
288
|
+
"token",
|
|
289
|
+
"two-factor",
|
|
290
|
+
"verify-email"
|
|
291
|
+
]);
|
|
292
|
+
const SENSITIVE_KEY_PARTS = [
|
|
293
|
+
"apikey",
|
|
294
|
+
"authorization",
|
|
295
|
+
"backupcode",
|
|
296
|
+
"cardnumber",
|
|
297
|
+
"cookie",
|
|
298
|
+
"credential",
|
|
299
|
+
"creditcard",
|
|
300
|
+
"passwd",
|
|
301
|
+
"password",
|
|
302
|
+
"privatekey",
|
|
303
|
+
"recoverycode",
|
|
304
|
+
"secret",
|
|
305
|
+
"signature",
|
|
306
|
+
"token",
|
|
307
|
+
"totp",
|
|
308
|
+
"twofactor",
|
|
309
|
+
"verificationcode"
|
|
310
|
+
];
|
|
311
|
+
const SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
312
|
+
"auth",
|
|
313
|
+
"code",
|
|
314
|
+
"csc",
|
|
315
|
+
"cvc",
|
|
316
|
+
"cvv",
|
|
317
|
+
"iban",
|
|
318
|
+
"jwt",
|
|
319
|
+
"otp",
|
|
320
|
+
"pin",
|
|
321
|
+
"pwd",
|
|
322
|
+
"sessionid",
|
|
323
|
+
"sid",
|
|
324
|
+
"ssn"
|
|
325
|
+
]);
|
|
326
|
+
const SENSITIVE_HEADER_SET = new Set(LT_BUG_SENSITIVE_HEADERS);
|
|
327
|
+
const BODY_EXCLUDED = new RegExp(`/(?:${LT_BUG_BODY_EXCLUDED_SEGMENTS.map((segment) => segment.replace(/[-/]/g, "\\$&")).join("|")})(?:[/?#;]|$)`, "i");
|
|
328
|
+
const USERINFO = /\b([a-z][a-z\d+.-]*:\/\/)[^\s/?#@:]+:[^\s/?#@]+@/gi;
|
|
329
|
+
const JWT = /\beyJ[\w-]{5,}\.[\w-]{5,}\.[\w-]*/g;
|
|
330
|
+
const BEARER = /\b(Bearer)\s+(?=[\w\-.~+/]*[\d.\-_~+/])[\w\-.~+/]{8,}=*/g;
|
|
331
|
+
const PATH_TOKEN = /(\/(?:activate|confirm-email|email-verification|invitation|invite|magic-link|reset-password|verify-email)\/)[^/?#\s"'<>]+/gi;
|
|
332
|
+
const PARAM = /(^|[?&#;\s"'(,{]|%3[fF]|%26|%23)((?:[\w.\-~[\]]|%5[bBdD])+)(=|%3[dD])([^&#;\s"'<>)}]*?)(?=$|[&#;\s"'<>)}]|%26|%23)/g;
|
|
333
|
+
const JSON_PAIR = /("((?:[^"\\]|\\.)*)"\s*:\s*)("(?:[^"\\]|\\.)*"|-?\d[\d.eE+-]*|true|false|null)/g;
|
|
334
|
+
const JSON_PAIR_OPEN = /("((?:[^"\\]|\\.)*)"\s*:\s*)"(?:[^"\\]|\\.)*$/;
|
|
335
|
+
const LABELLED = /\b(api[-_]?key|otp|passwd|password|secret|token)(\s*:\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi;
|
|
336
|
+
function isLtSensitiveHeader(name) {
|
|
337
|
+
return SENSITIVE_HEADER_SET.has(name.trim().toLowerCase());
|
|
338
|
+
}
|
|
339
|
+
function isLtSensitiveKey(key) {
|
|
340
|
+
const decoded = safeDecode(key);
|
|
341
|
+
const normalized = (decoded.split(/[[\].]/).filter(Boolean).pop() ?? decoded).toLowerCase().replace(/[-_\s]/g, "");
|
|
342
|
+
return SENSITIVE_KEYS.has(normalized) || SENSITIVE_KEY_PARTS.some((part) => normalized.includes(part));
|
|
343
|
+
}
|
|
344
|
+
function isLtBodyExcludedUrl(url) {
|
|
345
|
+
const path = url.replace(/^[a-z][a-z\d+.-]*:\/\/[^/?#]*/i, "").split(/[?#]/)[0] ?? "";
|
|
346
|
+
return BODY_EXCLUDED.test(path);
|
|
347
|
+
}
|
|
348
|
+
function redactLtText(text) {
|
|
349
|
+
if (!text) return text;
|
|
350
|
+
let result = text.replace(USERINFO, `$1${LT_BUG_REDACTED}@`).replace(JWT, LT_BUG_REDACTED).replace(BEARER, `$1 ${LT_BUG_REDACTED}`).replace(PATH_TOKEN, `$1${LT_BUG_REDACTED}`);
|
|
351
|
+
result = redactParams(result, 0);
|
|
352
|
+
result = result.replace(JSON_PAIR, (match, prefix, key) => isLtSensitiveKey(key) ? `${prefix}"${LT_BUG_REDACTED}"` : match);
|
|
353
|
+
result = result.replace(JSON_PAIR_OPEN, (match, prefix, key) => isLtSensitiveKey(key) ? `${prefix}"${LT_BUG_REDACTED}"` : match);
|
|
354
|
+
return result.replace(LABELLED, `$1$2${LT_BUG_REDACTED}`);
|
|
355
|
+
}
|
|
356
|
+
function redactLtUrl(url) {
|
|
357
|
+
return redactLtText(url);
|
|
358
|
+
}
|
|
359
|
+
function redactLtValue(value, depth = 0) {
|
|
360
|
+
if (typeof value === "string") return redactLtText(value);
|
|
361
|
+
if (depth > 20 || value === null || typeof value !== "object") return depth > 20 ? LT_BUG_REDACTED : value;
|
|
362
|
+
if (Array.isArray(value)) return value.map((item) => redactLtValue(item, depth + 1));
|
|
363
|
+
const result = {};
|
|
364
|
+
for (const [key, item] of Object.entries(value)) result[key] = isLtSensitiveKey(key) ? LT_BUG_REDACTED : redactLtValue(item, depth + 1);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
function redactLtBody(body, maxLength = 1e3) {
|
|
368
|
+
const input = body.length > 2e4 ? body.slice(0, 2e4) : body;
|
|
369
|
+
const trimmed = input.trim();
|
|
370
|
+
let redacted;
|
|
371
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) try {
|
|
372
|
+
redacted = JSON.stringify(redactLtValue(JSON.parse(trimmed)));
|
|
373
|
+
} catch {
|
|
374
|
+
redacted = redactLtText(input);
|
|
375
|
+
}
|
|
376
|
+
else redacted = redactLtText(input);
|
|
377
|
+
return truncateLt(redacted, maxLength);
|
|
378
|
+
}
|
|
379
|
+
function redactLtHeaders(headers) {
|
|
380
|
+
const result = {};
|
|
381
|
+
for (const [name, value] of Object.entries(headers)) result[name] = isLtSensitiveHeader(name) ? LT_BUG_REDACTED : redactLtText(String(value));
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
function truncateLt(text, maxLength) {
|
|
385
|
+
return text.length <= maxLength ? text : `${text.slice(0, maxLength)}... [truncated ${text.length - maxLength} characters]`;
|
|
386
|
+
}
|
|
387
|
+
function redactParams(text, depth) {
|
|
388
|
+
return text.replace(PARAM, (match, separator, key, equals, value) => {
|
|
389
|
+
if (isLtSensitiveKey(key)) return `${separator}${key}${equals}${value ? LT_BUG_REDACTED : ""}`;
|
|
390
|
+
if (depth < 3 && /=|%3[dD]/.test(value)) return `${separator}${key}${equals}${redactParams(value, depth + 1)}`;
|
|
391
|
+
return match;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function safeDecode(value) {
|
|
395
|
+
try {
|
|
396
|
+
return decodeURIComponent(value);
|
|
397
|
+
} catch {
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const LT_BUG_REPORT_TYPES = [
|
|
402
|
+
"bug",
|
|
403
|
+
"feature",
|
|
404
|
+
"enhancement",
|
|
405
|
+
"other"
|
|
406
|
+
];
|
|
407
|
+
const LT_BUG_REPORT_LIMITS = Object.freeze({
|
|
408
|
+
attachments: 10,
|
|
409
|
+
attachmentDataUrl: 15e6,
|
|
410
|
+
consoleLogs: 100,
|
|
411
|
+
description: 2e4,
|
|
412
|
+
networkRequests: 100,
|
|
413
|
+
text: 1e4,
|
|
414
|
+
title: 255,
|
|
415
|
+
userInteractions: 200
|
|
416
|
+
});
|
|
417
|
+
const LT_BUG_MISSING_FIELDS = "Missing required fields: title, type, and description are required";
|
|
418
|
+
const DATA_URL = /^data:([\w.+-]+\/[\w.+-]+)?((?:;[\w.+-]+=[^;,]*)*);base64,/i;
|
|
419
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
420
|
+
const MIME = /^[\w.+-]+\/[\w.+-]+$/;
|
|
421
|
+
function validateLtBugReport(body) {
|
|
422
|
+
if (!isRecord(body)) return invalid("The bug report must be a JSON object");
|
|
423
|
+
const title = typeof body["title"] === "string" ? body["title"].trim() : "";
|
|
424
|
+
const description = typeof body["description"] === "string" ? body["description"].trim() : "";
|
|
425
|
+
const type = body["type"];
|
|
426
|
+
if (!title || !description || typeof type !== "string" || !type) return invalid(LT_BUG_MISSING_FIELDS);
|
|
427
|
+
if (!LT_BUG_REPORT_TYPES.includes(type)) return invalid(`type must be one of ${LT_BUG_REPORT_TYPES.join(", ")}`);
|
|
428
|
+
for (const key of [
|
|
429
|
+
"expectedBehavior",
|
|
430
|
+
"stepsToReproduce",
|
|
431
|
+
"screenshot"
|
|
432
|
+
]) if (body[key] !== void 0 && body[key] !== null && typeof body[key] !== "string") return invalid(`${key} must be a string`);
|
|
433
|
+
for (const key of [
|
|
434
|
+
"attachments",
|
|
435
|
+
"consoleLogs",
|
|
436
|
+
"networkRequests",
|
|
437
|
+
"userInteractions"
|
|
438
|
+
]) if (body[key] !== void 0 && body[key] !== null && !Array.isArray(body[key])) return invalid(`${key} must be an array`);
|
|
439
|
+
if (body["browserInfo"] !== void 0 && body["browserInfo"] !== null && !isRecord(body["browserInfo"])) return invalid("browserInfo must be an object");
|
|
440
|
+
const files = [];
|
|
441
|
+
let skippedAttachments = 0;
|
|
442
|
+
const rawAttachments = body["attachments"] ?? [];
|
|
443
|
+
for (const [index, raw] of rawAttachments.entries()) {
|
|
444
|
+
if (!isRecord(raw) || typeof raw["data"] !== "string") return invalid(`attachments[${index}] needs a data URL`);
|
|
445
|
+
const file = decodeDataUrl(raw["data"], typeof raw["type"] === "string" ? raw["type"] : void 0);
|
|
446
|
+
if (file === "invalid") return invalid(`attachments[${index}] is not a base64 data URL`);
|
|
447
|
+
if (file === "too-large" || files.length >= LT_BUG_REPORT_LIMITS.attachments || file.data.length === 0) {
|
|
448
|
+
skippedAttachments++;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
files.push({
|
|
452
|
+
...file,
|
|
453
|
+
isScreenshot: raw["isScreenshot"] === true,
|
|
454
|
+
name: fileName(raw["name"], index)
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const screenshot = typeof body["screenshot"] === "string" && body["screenshot"] ? body["screenshot"] : void 0;
|
|
458
|
+
if (screenshot && !files.some((file) => file.isScreenshot)) {
|
|
459
|
+
const file = decodeDataUrl(screenshot, void 0);
|
|
460
|
+
if (file === "invalid" || file !== "too-large" && !file.contentType.startsWith("image/")) return invalid("screenshot is not a base64 image data URL");
|
|
461
|
+
if (file === "too-large" || files.length >= LT_BUG_REPORT_LIMITS.attachments) skippedAttachments++;
|
|
462
|
+
else files.push({
|
|
463
|
+
...file,
|
|
464
|
+
isScreenshot: true,
|
|
465
|
+
name: `screenshot-${Date.now()}.png`
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
ok: true,
|
|
470
|
+
report: {
|
|
471
|
+
attachments: files,
|
|
472
|
+
browserInfo: browserInfo(body["browserInfo"]),
|
|
473
|
+
consoleLogs: newest(body["consoleLogs"], LT_BUG_REPORT_LIMITS.consoleLogs).flatMap(consoleLog),
|
|
474
|
+
description: cleanText(description, LT_BUG_REPORT_LIMITS.description),
|
|
475
|
+
expectedBehavior: optionalText(body["expectedBehavior"]),
|
|
476
|
+
networkRequests: newest(body["networkRequests"], LT_BUG_REPORT_LIMITS.networkRequests).flatMap(networkRequest),
|
|
477
|
+
skippedAttachments,
|
|
478
|
+
stepsToReproduce: optionalText(body["stepsToReproduce"]),
|
|
479
|
+
title: cleanText(title, LT_BUG_REPORT_LIMITS.title).replace(/\s+/g, " "),
|
|
480
|
+
type,
|
|
481
|
+
userInteractions: newest(body["userInteractions"], LT_BUG_REPORT_LIMITS.userInteractions).flatMap(interaction)
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function invalid(error) {
|
|
486
|
+
return {
|
|
487
|
+
error,
|
|
488
|
+
ok: false
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function isRecord(value) {
|
|
492
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
493
|
+
}
|
|
494
|
+
function newest(value, limit) {
|
|
495
|
+
return Array.isArray(value) ? value.slice(-limit) : [];
|
|
496
|
+
}
|
|
497
|
+
function cleanText(text, max) {
|
|
498
|
+
return truncateLt(redactLtText(text.length > max * 2 ? text.slice(0, max * 2) : text), max);
|
|
499
|
+
}
|
|
500
|
+
function optionalText(value) {
|
|
501
|
+
return typeof value === "string" && value.trim() ? cleanText(value.trim(), LT_BUG_REPORT_LIMITS.text) : void 0;
|
|
502
|
+
}
|
|
503
|
+
function str(value, max) {
|
|
504
|
+
return typeof value === "string" ? cleanText(value, max) : void 0;
|
|
505
|
+
}
|
|
506
|
+
function num(value) {
|
|
507
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
508
|
+
}
|
|
509
|
+
function decodeDataUrl(dataUrl, declaredType) {
|
|
510
|
+
const header = DATA_URL.exec(dataUrl.slice(0, 300));
|
|
511
|
+
if (!header) return "invalid";
|
|
512
|
+
if (dataUrl.length > LT_BUG_REPORT_LIMITS.attachmentDataUrl) return "too-large";
|
|
513
|
+
const payload = dataUrl.slice(header[0].length);
|
|
514
|
+
if (!BASE64.test(payload)) return "invalid";
|
|
515
|
+
const fromUrl = header[1]?.toLowerCase();
|
|
516
|
+
return {
|
|
517
|
+
contentType: declaredType && MIME.test(declaredType) ? declaredType.toLowerCase() : fromUrl && MIME.test(fromUrl) ? fromUrl : "application/octet-stream",
|
|
518
|
+
data: Buffer.from(payload, "base64")
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function fileName(value, index) {
|
|
522
|
+
const base = (typeof value === "string" ? value : "").split(/[/\\]/).pop() ?? "";
|
|
523
|
+
return (Array.from(base).filter((char) => char.charCodeAt(0) >= 32 && char.charCodeAt(0) !== 127).join("").trim() || `attachment-${index + 1}`).slice(0, 200);
|
|
524
|
+
}
|
|
525
|
+
function browserInfo(value) {
|
|
526
|
+
if (!isRecord(value)) return;
|
|
527
|
+
const pair = (source) => isRecord(source) ? {
|
|
528
|
+
name: str(source["name"], 100),
|
|
529
|
+
version: str(source["version"], 100)
|
|
530
|
+
} : void 0;
|
|
531
|
+
const viewport = isRecord(value["viewport"]) ? {
|
|
532
|
+
height: num(value["viewport"]["height"]) ?? 0,
|
|
533
|
+
width: num(value["viewport"]["width"]) ?? 0
|
|
534
|
+
} : void 0;
|
|
535
|
+
return {
|
|
536
|
+
browser: pair(value["browser"]),
|
|
537
|
+
language: str(value["language"], 50),
|
|
538
|
+
os: pair(value["os"]),
|
|
539
|
+
platform: str(value["platform"], 100),
|
|
540
|
+
timestamp: str(value["timestamp"], 50),
|
|
541
|
+
url: typeof value["url"] === "string" ? truncateLt(redactLtUrl(value["url"].slice(0, 4e3)), 2e3) : void 0,
|
|
542
|
+
userAgent: str(value["userAgent"], 500),
|
|
543
|
+
viewport
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function consoleLog(value) {
|
|
547
|
+
if (!isRecord(value)) return [];
|
|
548
|
+
const parts = Array.isArray(value["message"]) ? value["message"] : [value["message"]];
|
|
549
|
+
return [{
|
|
550
|
+
level: [
|
|
551
|
+
"debug",
|
|
552
|
+
"error",
|
|
553
|
+
"info",
|
|
554
|
+
"log",
|
|
555
|
+
"warn"
|
|
556
|
+
].includes(String(value["level"])) ? String(value["level"]) : "log",
|
|
557
|
+
message: parts.slice(0, 20).map((part) => str(typeof part === "string" ? part : String(part ?? ""), 2e3) ?? "").filter(Boolean),
|
|
558
|
+
stack: str(value["stack"], 5e3),
|
|
559
|
+
timestamp: str(value["timestamp"], 50) ?? ""
|
|
560
|
+
}];
|
|
561
|
+
}
|
|
562
|
+
function networkRequest(value) {
|
|
563
|
+
if (!isRecord(value)) return [];
|
|
564
|
+
const url = typeof value["url"] === "string" ? value["url"].slice(0, 4e3) : "";
|
|
565
|
+
const responseBody = typeof value["responseBody"] === "string" && !isLtBodyExcludedUrl(url) ? redactLtBody(value["responseBody"], 1e3) : void 0;
|
|
566
|
+
return [{
|
|
567
|
+
error: str(value["error"], 500),
|
|
568
|
+
method: (str(value["method"], 16) ?? "GET").toUpperCase(),
|
|
569
|
+
responseBody,
|
|
570
|
+
status: num(value["status"]) ?? 0,
|
|
571
|
+
statusText: str(value["statusText"], 200) ?? "",
|
|
572
|
+
timestamp: str(value["timestamp"], 50) ?? "",
|
|
573
|
+
url: truncateLt(redactLtUrl(url), 2e3)
|
|
574
|
+
}];
|
|
575
|
+
}
|
|
576
|
+
function interaction(value) {
|
|
577
|
+
if (!isRecord(value)) return [];
|
|
578
|
+
const metadata = isRecord(value["metadata"]) ? value["metadata"] : void 0;
|
|
579
|
+
return [{
|
|
580
|
+
metadata: metadata ? {
|
|
581
|
+
errorMessage: str(metadata["errorMessage"], 500),
|
|
582
|
+
key: str(metadata["key"], 50),
|
|
583
|
+
text: str(metadata["text"], 500),
|
|
584
|
+
toUrl: str(metadata["toUrl"], 2e3),
|
|
585
|
+
url: str(metadata["url"], 2e3),
|
|
586
|
+
value: str(metadata["value"], 500)
|
|
587
|
+
} : void 0,
|
|
588
|
+
target: str(value["target"], 200) ?? "",
|
|
589
|
+
timestamp: str(value["timestamp"], 50) ?? "",
|
|
590
|
+
type: str(value["type"], 50) ?? "click"
|
|
591
|
+
}];
|
|
592
|
+
}
|
|
593
|
+
const LT_BUG_SERVER_DEFAULT_ENDPOINT = "/_bug-lt/report";
|
|
594
|
+
const LT_BUG_DEFAULT_MAX_BODY_BYTES = 31457280;
|
|
595
|
+
const PREFIX = "[@lenne.tech/ng-base/bug/server]";
|
|
596
|
+
var HttpError = class extends Error {
|
|
597
|
+
status;
|
|
598
|
+
constructor(status, message) {
|
|
599
|
+
super(message);
|
|
600
|
+
this.status = status;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
function isLtBugServerEnabled(enabled, env) {
|
|
604
|
+
const switchValue = env["NG_PUBLIC_LT_BUG_ENABLED"]?.trim() ?? "";
|
|
605
|
+
if (/^(true|1|yes|on)$/i.test(switchValue)) return true;
|
|
606
|
+
if (/^(false|0|no|off)$/i.test(switchValue)) return false;
|
|
607
|
+
if (typeof enabled === "boolean") return enabled;
|
|
608
|
+
const appEnv = env["NG_PUBLIC_APP_ENV"]?.trim().toLowerCase();
|
|
609
|
+
return !!appEnv && appEnv !== "production" && appEnv !== "prod";
|
|
610
|
+
}
|
|
611
|
+
function ltBugReport(options = {}) {
|
|
612
|
+
const endpoint = options.endpoint ?? "/_bug-lt/report";
|
|
613
|
+
const limiter = createRateLimiter(options.rateLimit === void 0 ? {
|
|
614
|
+
max: 20,
|
|
615
|
+
windowMs: 6e5
|
|
616
|
+
} : options.rateLimit);
|
|
617
|
+
const logger = options.logger ?? console;
|
|
618
|
+
return (req, res, next) => {
|
|
619
|
+
if ((req.url ?? "").split("?")[0] !== endpoint) {
|
|
620
|
+
next();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const env = options.env ?? processEnv();
|
|
624
|
+
if (!isLtBugServerEnabled(options.enabled, env)) {
|
|
625
|
+
sendJson(res, 404, {
|
|
626
|
+
error: "Not Found",
|
|
627
|
+
success: false
|
|
628
|
+
});
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
handle(req, res, env, options, limiter).catch((error) => {
|
|
632
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
633
|
+
if (!(error instanceof HttpError)) logger.error(`${PREFIX} Unexpected error:`, sanitize(error, linearConfig(env, options)?.apiKey));
|
|
634
|
+
if (!res.headersSent) sendJson(res, status, {
|
|
635
|
+
error: error instanceof HttpError ? error.message : "Internal server error while processing bug report",
|
|
636
|
+
success: false
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
async function handle(req, res, env, options, limiter) {
|
|
642
|
+
const logger = options.logger ?? console;
|
|
643
|
+
if (req.method !== "POST") {
|
|
644
|
+
res.setHeader("allow", "POST");
|
|
645
|
+
throw new HttpError(405, "Method Not Allowed");
|
|
646
|
+
}
|
|
647
|
+
if (!/^application\/json\b/i.test(headerValue(req.headers["content-type"]) ?? "")) throw new HttpError(415, "Content-Type must be application/json");
|
|
648
|
+
const retryAfter = limiter(clientKey(req));
|
|
649
|
+
if (retryAfter > 0) {
|
|
650
|
+
res.setHeader("retry-after", String(Math.ceil(retryAfter / 1e3)));
|
|
651
|
+
throw new HttpError(429, "Too many bug reports, please try again later");
|
|
652
|
+
}
|
|
653
|
+
const config = linearConfig(env, options);
|
|
654
|
+
if (!config) {
|
|
655
|
+
const missing = [!pick(env, "NG_LINEAR_API_KEY", options.linearApiKey) && "NG_LINEAR_API_KEY", !pick(env, "NG_LINEAR_TEAM_NAME", options.linearTeamName) && "NG_LINEAR_TEAM_NAME"].filter(Boolean);
|
|
656
|
+
logger.error(`${PREFIX} Linear is not configured: set ${missing.join(" and ")} on the server.`);
|
|
657
|
+
throw new HttpError(500, "Bug reporting is not configured on the server");
|
|
658
|
+
}
|
|
659
|
+
const validation = validateLtBugReport(await readJsonBody(req, options.maxBodyBytes ?? 31457280));
|
|
660
|
+
if (!validation.ok) throw new HttpError(400, validation.error);
|
|
661
|
+
let result;
|
|
662
|
+
try {
|
|
663
|
+
result = await createLtLinearIssue(validation.report, config, {
|
|
664
|
+
fetch: options.fetch,
|
|
665
|
+
logger,
|
|
666
|
+
timeZone: options.timeZone
|
|
667
|
+
});
|
|
668
|
+
} catch (error) {
|
|
669
|
+
if (error instanceof LtBugConfigError) {
|
|
670
|
+
logger.error(`${PREFIX} ${sanitize(error, config.apiKey)}`);
|
|
671
|
+
throw new HttpError(500, "Bug reporting is not configured on the server");
|
|
672
|
+
}
|
|
673
|
+
logger.error(`${PREFIX} Linear rejected the bug report:`, sanitize(error, config.apiKey));
|
|
674
|
+
throw new HttpError(502, "Linear rejected the bug report");
|
|
675
|
+
}
|
|
676
|
+
sendJson(res, 200, {
|
|
677
|
+
identifier: result.identifier,
|
|
678
|
+
issueId: result.issueId,
|
|
679
|
+
issueUrl: result.issueUrl,
|
|
680
|
+
message: "Bug report submitted successfully",
|
|
681
|
+
success: true
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
var LtBugConfigError = class extends Error {};
|
|
685
|
+
async function createLtLinearIssue(report, config, options = {}) {
|
|
686
|
+
const logger = options.logger ?? console;
|
|
687
|
+
const client = new LtLinearClient({
|
|
688
|
+
apiKey: config.apiKey,
|
|
689
|
+
fetch: options.fetch
|
|
690
|
+
});
|
|
691
|
+
const team = await client.findTeam(config.teamName);
|
|
692
|
+
if (!team) {
|
|
693
|
+
const available = (await client.getTeams().catch(() => [])).map((candidate) => candidate.name).join(", ");
|
|
694
|
+
throw new LtBugConfigError(`Linear team "${config.teamName}" not found (NG_LINEAR_TEAM_NAME). Available teams: ${available || "none"}`);
|
|
695
|
+
}
|
|
696
|
+
let projectId;
|
|
697
|
+
if (config.projectName) try {
|
|
698
|
+
projectId = (await client.findProject(config.projectName))?.id;
|
|
699
|
+
if (!projectId) logger.warn(`${PREFIX} Linear project "${config.projectName}" not found (NG_LINEAR_PROJECT_NAME); creating the issue without project.`);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
logger.warn(`${PREFIX} Could not resolve the Linear project, continuing without:`, sanitize(error, config.apiKey));
|
|
702
|
+
}
|
|
703
|
+
let labelId = null;
|
|
704
|
+
const label = LT_BUG_LABELS[report.type];
|
|
705
|
+
if (label) try {
|
|
706
|
+
labelId = await client.findLabel(team.id, label.name) ?? await client.createLabel(team.id, label.name, label.color);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
logger.warn(`${PREFIX} Could not find or create the label "${label.name}", continuing without:`, sanitize(error, config.apiKey));
|
|
709
|
+
}
|
|
710
|
+
const text = formatLtBugReportForLinear(report, { timeZone: options.timeZone });
|
|
711
|
+
const issue = await client.createIssue({
|
|
712
|
+
...text,
|
|
713
|
+
labelIds: labelId ? [labelId] : void 0,
|
|
714
|
+
projectId,
|
|
715
|
+
teamId: team.id
|
|
716
|
+
});
|
|
717
|
+
for (const file of report.attachments) try {
|
|
718
|
+
const assetUrl = await client.uploadFile(file.data, file.name, file.contentType);
|
|
719
|
+
await client.createAttachment(issue.id, assetUrl, file.name);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
logger.warn(`${PREFIX} Could not upload the attachment "${file.name}", continuing without:`, sanitize(error, config.apiKey));
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
identifier: issue.identifier,
|
|
725
|
+
issueId: issue.id,
|
|
726
|
+
issueUrl: issue.url
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function linearConfig(env, options) {
|
|
730
|
+
const apiKey = pick(env, "NG_LINEAR_API_KEY", options.linearApiKey);
|
|
731
|
+
const teamName = pick(env, "NG_LINEAR_TEAM_NAME", options.linearTeamName);
|
|
732
|
+
if (!apiKey || !teamName) return null;
|
|
733
|
+
return {
|
|
734
|
+
apiKey,
|
|
735
|
+
projectName: pick(env, "NG_LINEAR_PROJECT_NAME", options.linearProjectName),
|
|
736
|
+
teamName
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function pick(env, name, fallback) {
|
|
740
|
+
return env[name]?.trim() || fallback?.trim() || void 0;
|
|
741
|
+
}
|
|
742
|
+
function sanitize(error, apiKey) {
|
|
743
|
+
let text = error instanceof Error ? error.message : String(error);
|
|
744
|
+
if (apiKey) text = text.split(apiKey).join(LT_BUG_REDACTED);
|
|
745
|
+
return redactLtText(text.replace(/lin_api_[A-Za-z0-9]+/g, LT_BUG_REDACTED));
|
|
746
|
+
}
|
|
747
|
+
async function readJsonBody(req, limit) {
|
|
748
|
+
const parsed = req.body;
|
|
749
|
+
if (parsed !== void 0 && parsed !== null && typeof parsed === "object" && !Buffer.isBuffer(parsed)) return parsed;
|
|
750
|
+
if (Number(headerValue(req.headers["content-length"]) ?? 0) > limit) {
|
|
751
|
+
req.resume();
|
|
752
|
+
throw new HttpError(413, "Bug report too large");
|
|
753
|
+
}
|
|
754
|
+
const text = await new Promise((resolve, reject) => {
|
|
755
|
+
const chunks = [];
|
|
756
|
+
let size = 0;
|
|
757
|
+
let tooLarge = false;
|
|
758
|
+
req.on("data", (chunk) => {
|
|
759
|
+
if (tooLarge) return;
|
|
760
|
+
size += chunk.length;
|
|
761
|
+
if (size > limit) {
|
|
762
|
+
tooLarge = true;
|
|
763
|
+
chunks.length = 0;
|
|
764
|
+
reject(new HttpError(413, "Bug report too large"));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
chunks.push(chunk);
|
|
768
|
+
});
|
|
769
|
+
req.on("end", () => {
|
|
770
|
+
if (!tooLarge) resolve(Buffer.concat(chunks).toString("utf8"));
|
|
771
|
+
});
|
|
772
|
+
req.on("error", (error) => reject(error));
|
|
773
|
+
});
|
|
774
|
+
try {
|
|
775
|
+
return JSON.parse(text);
|
|
776
|
+
} catch {
|
|
777
|
+
throw new HttpError(400, "The bug report must be valid JSON");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function createRateLimiter(limit) {
|
|
781
|
+
if (!limit) return () => 0;
|
|
782
|
+
const windows = /* @__PURE__ */ new Map();
|
|
783
|
+
return (key) => {
|
|
784
|
+
const now = Date.now();
|
|
785
|
+
if (windows.size > 5e3) {
|
|
786
|
+
for (const [entryKey, entry] of windows) if (entry.resetAt <= now) windows.delete(entryKey);
|
|
787
|
+
}
|
|
788
|
+
const entry = windows.get(key);
|
|
789
|
+
if (!entry || entry.resetAt <= now) {
|
|
790
|
+
windows.set(key, {
|
|
791
|
+
count: 1,
|
|
792
|
+
resetAt: now + limit.windowMs
|
|
793
|
+
});
|
|
794
|
+
return 0;
|
|
795
|
+
}
|
|
796
|
+
entry.count++;
|
|
797
|
+
return entry.count > limit.max ? entry.resetAt - now : 0;
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function clientKey(req) {
|
|
801
|
+
return headerValue(req.headers["x-forwarded-for"])?.split(",").pop()?.trim() || req.socket?.remoteAddress || "unknown";
|
|
802
|
+
}
|
|
803
|
+
function sendJson(res, status, body) {
|
|
804
|
+
res.statusCode = status;
|
|
805
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
806
|
+
res.setHeader("cache-control", "no-store");
|
|
807
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
808
|
+
res.end(JSON.stringify(body));
|
|
809
|
+
}
|
|
810
|
+
function headerValue(value) {
|
|
811
|
+
return Array.isArray(value) ? value[0] : value;
|
|
812
|
+
}
|
|
813
|
+
function processEnv() {
|
|
814
|
+
return globalThis.process?.env ?? {};
|
|
815
|
+
}
|
|
816
|
+
export { LT_BUG_BODY_EXCLUDED_SEGMENTS, LT_BUG_DEFAULT_MAX_BODY_BYTES, LT_BUG_LABELS, LT_BUG_MISSING_FIELDS, LT_BUG_REDACTED, LT_BUG_REPORT_LIMITS, LT_BUG_REPORT_TYPES, LT_BUG_SENSITIVE_HEADERS, LT_BUG_SERVER_DEFAULT_ENDPOINT, LT_LINEAR_API_URL, LtBugConfigError, LtLinearClient, LtLinearError, createLtLinearIssue, formatLtBugReportForLinear, isLtBodyExcludedUrl, isLtBugServerEnabled, isLtSensitiveHeader, isLtSensitiveKey, ltBugReport, redactLtBody, redactLtHeaders, redactLtText, redactLtUrl, validateLtBugReport };
|
|
817
|
+
|
|
818
|
+
//# sourceMappingURL=lenne.tech-ng-base-bug-server.mjs.map
|