better-ship 0.33.0 → 0.35.0
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/dist/browser-support/floor.d.ts +43 -0
- package/dist/browser-support/floor.d.ts.map +1 -0
- package/dist/browser-support/floor.js +3 -0
- package/dist/browser-support/oxlint.d.ts +6 -0
- package/dist/browser-support/oxlint.d.ts.map +1 -0
- package/dist/browser-support/oxlint.js +175 -0
- package/dist/browser-support/oxlint.js.map +1 -0
- package/dist/browser-support/polyfills.d.ts +10 -0
- package/dist/browser-support/polyfills.d.ts.map +1 -0
- package/dist/browser-support/polyfills.js +3 -0
- package/dist/browser-support/update-screen.d.ts +51 -0
- package/dist/browser-support/update-screen.d.ts.map +1 -0
- package/dist/browser-support/update-screen.js +107 -0
- package/dist/browser-support/update-screen.js.map +1 -0
- package/dist/floor-Cuw8EIhg.js +146 -0
- package/dist/floor-Cuw8EIhg.js.map +1 -0
- package/dist/polyfills-DEQFkqRI.js +28 -0
- package/dist/polyfills-DEQFkqRI.js.map +1 -0
- package/dist/sentry/client.d.ts +21 -0
- package/dist/sentry/client.d.ts.map +1 -1
- package/dist/sentry/client.js +23 -1
- package/dist/sentry/client.js.map +1 -1
- package/package.json +21 -1
- package/src/infrastructure/browser-support/.browserslistrc +5 -0
- package/src/infrastructure/browser-support/browser-floor.error.ts +31 -0
- package/src/infrastructure/browser-support/floor.ts +145 -0
- package/src/infrastructure/browser-support/oxlint/index.ts +13 -0
- package/src/infrastructure/browser-support/oxlint/no-unsupported-api.ts +219 -0
- package/src/infrastructure/browser-support/polyfills.ts +25 -0
- package/src/infrastructure/browser-support/update-screen.ts +142 -0
- package/src/infrastructure/sentry/client.ts +41 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/** Set on `documentElement` when the update screen is showing. Hydration checks this name. */
|
|
2
|
+
export const unsupportedBrowserAttribute = 'data-unsupported-browser'
|
|
3
|
+
|
|
4
|
+
const updateTitleAttribute = 'data-update-title' as const
|
|
5
|
+
const updateBodyAttribute = 'data-update-body' as const
|
|
6
|
+
const fallbackFont = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif'
|
|
7
|
+
|
|
8
|
+
/** Copy and public URLs for the update screen tags in the document head. */
|
|
9
|
+
export type UpdateScreenHead = {
|
|
10
|
+
readonly title: string
|
|
11
|
+
readonly body: string
|
|
12
|
+
/** Public URL of `updateScreenStyle`. */
|
|
13
|
+
readonly styleHref: string
|
|
14
|
+
/** Public URL of `updateScreenScript`. */
|
|
15
|
+
readonly scriptSrc: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Stylesheet link a root route places in `head().links`. */
|
|
19
|
+
export type UpdateScreenLink = {
|
|
20
|
+
readonly rel: 'stylesheet'
|
|
21
|
+
readonly href: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Deferred script a root route places in `head().scripts`. */
|
|
25
|
+
export type UpdateScreenScript = {
|
|
26
|
+
readonly src: string
|
|
27
|
+
readonly defer: true
|
|
28
|
+
readonly 'data-update-title': string
|
|
29
|
+
readonly 'data-update-body': string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `links` and `scripts` a root route spreads into `head()`.
|
|
34
|
+
* The arrays are mutable so they match the `head()` return type.
|
|
35
|
+
*/
|
|
36
|
+
export type UpdateScreenHeadEntries = {
|
|
37
|
+
links: UpdateScreenLink[]
|
|
38
|
+
scripts: UpdateScreenScript[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stylesheet and deferred script entries for a root route `head()`.
|
|
43
|
+
* Spread `links` and `scripts` into `head()`. The router escapes the attribute values.
|
|
44
|
+
* `defer` keeps parsing unblocked. The script is classic ES5, so a browser below the floor can show the screen.
|
|
45
|
+
*/
|
|
46
|
+
export function updateScreenHeadScript(head: UpdateScreenHead): UpdateScreenHeadEntries {
|
|
47
|
+
return {
|
|
48
|
+
links: [{ rel: 'stylesheet', href: head.styleHref }],
|
|
49
|
+
scripts: [
|
|
50
|
+
{
|
|
51
|
+
src: head.scriptSrc,
|
|
52
|
+
defer: true,
|
|
53
|
+
[updateTitleAttribute]: head.title,
|
|
54
|
+
[updateBodyAttribute]: head.body,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** True when the update screen script marked this element. Skip hydration when it is true. */
|
|
61
|
+
export function isUnsupportedBrowser(root: {
|
|
62
|
+
readonly hasAttribute: (name: string) => boolean
|
|
63
|
+
}): boolean {
|
|
64
|
+
return root.hasAttribute(unsupportedBrowserAttribute)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Classic ES5 script. Serve it at `scriptSrc`.
|
|
69
|
+
* It shows the screen when `CSS.registerProperty` or `color-mix()` is missing, and does nothing at the floor.
|
|
70
|
+
*/
|
|
71
|
+
export const updateScreenScript = `(function () {
|
|
72
|
+
var cssApi = typeof CSS === 'undefined' ? null : CSS;
|
|
73
|
+
var hasProperty = cssApi !== null && typeof cssApi.registerProperty === 'function';
|
|
74
|
+
var hasMix = cssApi !== null && typeof cssApi.supports === 'function' && cssApi.supports('color', 'color-mix(in srgb, red, blue)');
|
|
75
|
+
if (hasProperty && hasMix) return;
|
|
76
|
+
document.documentElement.setAttribute('${unsupportedBrowserAttribute}', '');
|
|
77
|
+
var scripts = document.getElementsByTagName('script');
|
|
78
|
+
var title = 'Update your browser';
|
|
79
|
+
var body = '';
|
|
80
|
+
for (var index = 0; index < scripts.length; index = index + 1) {
|
|
81
|
+
var marker = scripts[index].getAttribute('${updateTitleAttribute}');
|
|
82
|
+
if (marker === null) continue;
|
|
83
|
+
title = marker;
|
|
84
|
+
var text = scripts[index].getAttribute('${updateBodyAttribute}');
|
|
85
|
+
body = text === null ? '' : text;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
var screen = document.createElement('div');
|
|
89
|
+
screen.id = 'unsupported-browser';
|
|
90
|
+
var heading = document.createElement('h1');
|
|
91
|
+
heading.className = 'unsupported-browser-title';
|
|
92
|
+
heading.textContent = title;
|
|
93
|
+
var paragraph = document.createElement('p');
|
|
94
|
+
paragraph.className = 'unsupported-browser-body';
|
|
95
|
+
paragraph.textContent = body;
|
|
96
|
+
screen.appendChild(heading);
|
|
97
|
+
screen.appendChild(paragraph);
|
|
98
|
+
document.body.appendChild(screen);
|
|
99
|
+
})();
|
|
100
|
+
`
|
|
101
|
+
|
|
102
|
+
// `!important` keeps application styles from painting over the update screen.
|
|
103
|
+
/** Classic CSS for the update screen. Serve it at `styleHref`. Tokens fall back to neutral grey and a system sans-serif. */
|
|
104
|
+
export const updateScreenStyle = `html[${unsupportedBrowserAttribute}],
|
|
105
|
+
html[${unsupportedBrowserAttribute}] body {
|
|
106
|
+
margin: 0;
|
|
107
|
+
min-height: 100%;
|
|
108
|
+
background: var(--band, #f5f5f5);
|
|
109
|
+
}
|
|
110
|
+
html[${unsupportedBrowserAttribute}] body > :not(#unsupported-browser) {
|
|
111
|
+
display: none !important;
|
|
112
|
+
}
|
|
113
|
+
#unsupported-browser {
|
|
114
|
+
box-sizing: border-box;
|
|
115
|
+
min-height: 100vh;
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding: 48px 24px;
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
align-items: center;
|
|
122
|
+
text-align: center;
|
|
123
|
+
background: var(--band, #f5f5f5);
|
|
124
|
+
color: var(--foreground, #171717);
|
|
125
|
+
font-family: var(--font-text, ${fallbackFont});
|
|
126
|
+
}
|
|
127
|
+
.unsupported-browser-title {
|
|
128
|
+
margin: 0 0 12px;
|
|
129
|
+
max-width: 28rem;
|
|
130
|
+
font-family: var(--font-brand, ${fallbackFont});
|
|
131
|
+
font-size: 2rem;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
line-height: 1.2;
|
|
134
|
+
}
|
|
135
|
+
.unsupported-browser-body {
|
|
136
|
+
margin: 0;
|
|
137
|
+
max-width: 32rem;
|
|
138
|
+
color: var(--muted-foreground, #525252);
|
|
139
|
+
font-size: 1.0625rem;
|
|
140
|
+
line-height: 1.5;
|
|
141
|
+
}
|
|
142
|
+
`
|
|
@@ -33,6 +33,47 @@ export function createSentryLoggerErrorHook(): LoggerErrorHook {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** The error value as Sentry's React handler receives it. */
|
|
37
|
+
type ReactRootError = Parameters<ReturnType<typeof Sentry.reactErrorHandler>>[0]
|
|
38
|
+
|
|
39
|
+
/** The details React passes to a root error option; React may omit the component stack. */
|
|
40
|
+
export type ReactRootErrorInfo = { readonly componentStack?: string | undefined }
|
|
41
|
+
|
|
42
|
+
/** A `hydrateRoot` error option in React's own shape. */
|
|
43
|
+
type ReactRootErrorHandler = (error: ReactRootError, errorInfo: ReactRootErrorInfo) => void
|
|
44
|
+
|
|
45
|
+
/** The `hydrateRoot` error options; each one replaces React's console default. */
|
|
46
|
+
export type SentryReactRootOptions = {
|
|
47
|
+
readonly onCaughtError: ReactRootErrorHandler
|
|
48
|
+
readonly onUncaughtError: ReactRootErrorHandler
|
|
49
|
+
readonly onRecoverableError: ReactRootErrorHandler
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Report React errors to Sentry from `hydrateRoot`, as Sentry's React 19 setup does.
|
|
54
|
+
* Covers errors that a boundary catches, errors that no boundary catches, and hydration
|
|
55
|
+
* errors that React recovers from. TanStack redirects and not-found control flow are not
|
|
56
|
+
* reported. Each error still goes to the console, as with React's defaults.
|
|
57
|
+
*/
|
|
58
|
+
export function createSentryReactRootOptions(): SentryReactRootOptions {
|
|
59
|
+
const handler = Sentry.reactErrorHandler((error, errorInfo) => {
|
|
60
|
+
console.error(error, errorInfo.componentStack)
|
|
61
|
+
})
|
|
62
|
+
// Sentry's handler takes `null` for a missing component stack.
|
|
63
|
+
const report: ReactRootErrorHandler = (error, { componentStack }) => {
|
|
64
|
+
handler(error, { componentStack: componentStack ?? null })
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
// TanStack Router renders not-found through a React error boundary, so each 404 arrives here.
|
|
68
|
+
onCaughtError: (error, errorInfo) => {
|
|
69
|
+
if (isNotFound(error) || isRedirect(error)) return
|
|
70
|
+
report(error, errorInfo)
|
|
71
|
+
},
|
|
72
|
+
onUncaughtError: report,
|
|
73
|
+
onRecoverableError: report,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
36
77
|
/** Initialize once in the browser entrypoint before hydration; server imports have no side effects. */
|
|
37
78
|
export function initializeSentry(options: SentryClientOptions): ReturnType<typeof Sentry.init> {
|
|
38
79
|
const client = Sentry.init(createSentryClientOptions(options))
|