better-ship 0.34.0 → 0.35.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.
@@ -0,0 +1,43 @@
1
+ import { Xt as AppError } from "../index-DUFS0SJ_.js";
2
+ //#region src/infrastructure/browser-support/browser-floor.error.d.ts
3
+ /** The browserslist file is missing, empty, or not a query browserslist can resolve. */
4
+ export declare class BrowserFloorError extends AppError {
5
+ readonly path: string;
6
+ readonly reason: 'missing' | 'empty' | 'invalid';
7
+ readonly _tag = "BrowserFloorError";
8
+ readonly classification = "terminal";
9
+ /**
10
+ * `path` is the file or directory passed to `browserFloor`.
11
+ * `reason` is `missing` when no config is found, `empty` when the query matches no browsers,
12
+ * and `invalid` when browserslist rejects the query. `cause` is that rejection.
13
+ */
14
+ constructor(path: string, reason: 'missing' | 'empty' | 'invalid', cause?: unknown);
15
+ }
16
+ //#endregion
17
+ //#region src/infrastructure/browser-support/floor.d.ts
18
+ /** Oldest and newest version of one browser that the floor still includes. */
19
+ export type BrowserFloorRelease = {
20
+ readonly browser: string;
21
+ readonly minimum: string;
22
+ readonly maximum: string;
23
+ };
24
+ /** Title and body for the update screen, taken from the floor versions. */
25
+ export type UpdateScreenCopy = {
26
+ readonly title: string;
27
+ readonly body: string;
28
+ };
29
+ /**
30
+ * Resolve the minimum and maximum version of each browser in the browserslist config.
31
+ * The path is a config file or any file inside the project that owns `.browserslistrc`.
32
+ * Throws `BrowserFloorError` when the config is missing, empty, or invalid.
33
+ */
34
+ export declare function browserFloor(path: string): readonly BrowserFloorRelease[];
35
+ /**
36
+ * Vite `environments.client.build.target` values for the browserslist config at `path`.
37
+ * Throws `BrowserFloorError` when the config is missing, empty, or invalid.
38
+ */
39
+ export declare function buildTargets(path: string): readonly string[];
40
+ /** Sentence that names the floor versions. Pass the result of `browserFloor`. */
41
+ export declare function updateScreenCopy(releases: readonly BrowserFloorRelease[]): UpdateScreenCopy;
42
+ //#endregion
43
+ //# sourceMappingURL=floor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floor.d.ts","names":[],"sources":["../../src/infrastructure/browser-support/browser-floor.error.ts","../../src/infrastructure/browser-support/floor.ts"],"mappings":";;;qBAGa,0BAA0B;WAU1B;WACA;WAVF;WACA;;;;;;EAOT,YACW,cACA,yCACT;;;;;YCRQ;WACD;WACA;WACA;;;YAIC;WACD;WACA;;;;;;;wBAQK,aAAa,wBAAwB;;;;;wBAmBrC,aAAa;;wBAKb,iBAAiB,mBAAmB,wBAAwB"}
@@ -0,0 +1,3 @@
1
+ import { i as BrowserFloorError, n as buildTargets, r as updateScreenCopy, t as browserFloor } from "../floor-Cuw8EIhg.js";
2
+
3
+ export { BrowserFloorError, browserFloor, buildTargets, updateScreenCopy };
@@ -0,0 +1,6 @@
1
+ //#region src/infrastructure/browser-support/oxlint/index.d.ts
2
+ /** Oxlint plugin. Enable `browser-support/no-unsupported-api` in `jsPlugins`. */
3
+ declare const browserSupportPlugin: import("@oxlint/plugins").Plugin;
4
+ //#endregion
5
+ export { browserSupportPlugin as default };
6
+ //# sourceMappingURL=oxlint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oxlint.d.ts","names":[],"sources":["../../src/infrastructure/browser-support/oxlint/index.ts"],"mappings":";;cAKM,gDAAoB"}
@@ -0,0 +1,175 @@
1
+ import { i as BrowserFloorError, t as browserFloor } from "../floor-Cuw8EIhg.js";
2
+ import { t as POLYFILLED_APIS } from "../polyfills-DEQFkqRI.js";
3
+ import { defineRule, eslintCompatPlugin } from "@oxlint/plugins";
4
+ import bcd from "@mdn/browser-compat-data/forLegacyNode";
5
+
6
+ //#region src/infrastructure/browser-support/oxlint/no-unsupported-api.ts
7
+ const polyfilledApis = new Set(POLYFILLED_APIS);
8
+ /**
9
+ * Reject a global static call that a browser in the floor does not implement.
10
+ * `POLYFILLED_APIS` and a `// FALLBACK:` comment on the statement are allowed.
11
+ * The floor is the nearest `.browserslistrc` from the linted file.
12
+ */
13
+ const noUnsupportedApiRule = defineRule({
14
+ meta: {
15
+ type: "problem",
16
+ docs: { description: "Disallow a platform API that a browser in the browserslist floor does not implement." },
17
+ messages: {
18
+ unsupported: "{{api}} is missing in {{browsers}}.",
19
+ floor: "The browserslist floor for {{path}} is {{reason}}."
20
+ }
21
+ },
22
+ createOnce(context) {
23
+ const floors = /* @__PURE__ */ new Map();
24
+ const loadFloor = (path) => {
25
+ const cached = floors.get(path);
26
+ if (cached !== void 0) return cached;
27
+ try {
28
+ const releases = browserFloor(path);
29
+ floors.set(path, releases);
30
+ return releases;
31
+ } catch (cause) {
32
+ if (!(cause instanceof BrowserFloorError)) throw cause;
33
+ floors.set(path, cause);
34
+ return cause;
35
+ }
36
+ };
37
+ return {
38
+ Program(node) {
39
+ const floor = loadFloor(context.filename);
40
+ if (!(floor instanceof BrowserFloorError)) return;
41
+ context.report({
42
+ node,
43
+ messageId: "floor",
44
+ data: {
45
+ path: context.filename,
46
+ reason: floor.reason
47
+ }
48
+ });
49
+ },
50
+ MemberExpression(node) {
51
+ const api = staticApi(node);
52
+ if (api === void 0) return;
53
+ if (node.object.type !== "Identifier") return;
54
+ if (!isPlatformReference(context.sourceCode, node.object)) return;
55
+ if (markedFallback(context.sourceCode, node)) return;
56
+ if (polyfilledApis.has(api)) return;
57
+ const floor = loadFloor(context.filename);
58
+ if (floor instanceof BrowserFloorError) return;
59
+ const compat = compatFor(node.object.name, memberName(node));
60
+ if (compat === void 0) return;
61
+ const browsers = unsupportedBrowsers(compat, floor);
62
+ if (browsers.length === 0) return;
63
+ context.report({
64
+ node,
65
+ messageId: "unsupported",
66
+ data: {
67
+ api,
68
+ browsers: browsers.join(", ")
69
+ }
70
+ });
71
+ }
72
+ };
73
+ }
74
+ });
75
+ function staticApi(node) {
76
+ if (node.computed || node.object.type !== "Identifier" || node.property.type !== "Identifier") return;
77
+ return `${node.object.name}.${node.property.name}`;
78
+ }
79
+ function memberName(node) {
80
+ if (node.property.type !== "Identifier") return "";
81
+ return node.property.name;
82
+ }
83
+ function isPlatformReference(sourceCode, identifier) {
84
+ const variable = resolveVariable(sourceCode, identifier);
85
+ if (variable === null || variable.defs.length === 0) return true;
86
+ return variable.defs.every((definition) => definition.type === "ImplicitGlobalVariable");
87
+ }
88
+ function resolveVariable(sourceCode, identifier) {
89
+ let scope = sourceCode.getScope(identifier);
90
+ while (scope !== null) {
91
+ const variable = scope.set.get(identifier.name);
92
+ if (variable !== void 0) return variable;
93
+ scope = scope.upper;
94
+ }
95
+ return null;
96
+ }
97
+ function markedFallback(sourceCode, node) {
98
+ let current = node;
99
+ while (current !== null && current.type !== "Program") {
100
+ if ([...sourceCode.getCommentsBefore(current), ...sourceCode.getCommentsAfter(current)].some((comment) => comment.value.includes("FALLBACK:"))) return true;
101
+ current = current.parent;
102
+ }
103
+ return false;
104
+ }
105
+ function compatFor(objectName, name) {
106
+ const builtins = bcd.javascript.builtins;
107
+ return (builtins === void 0 ? void 0 : lookup(builtins, objectName, name)) ?? lookup(bcd.api, objectName, name);
108
+ }
109
+ function lookup(namespace, objectName, name) {
110
+ const objectData = namespace[objectName];
111
+ if (objectData === void 0) return void 0;
112
+ return objectData[`${name}_static`]?.__compat ?? objectData[name]?.__compat;
113
+ }
114
+ function unsupportedBrowsers(compat, floor) {
115
+ const missing = [];
116
+ for (const release of floor) {
117
+ const browser = compatBrowser(release.browser);
118
+ if (browser === void 0 || !rangeSupports(compat.support[browser], release)) missing.push(`${release.browser} ${release.minimum}`);
119
+ }
120
+ return missing;
121
+ }
122
+ function compatBrowser(browser) {
123
+ switch (browser) {
124
+ case "chrome":
125
+ case "edge":
126
+ case "firefox":
127
+ case "safari": return browser;
128
+ case "ios_saf": return "safari_ios";
129
+ default: return;
130
+ }
131
+ }
132
+ function rangeSupports(support, release) {
133
+ if (support === void 0) return false;
134
+ return (Array.isArray(support) ? support : [support]).some((statement) => statementSupports(statement, release));
135
+ }
136
+ function statementSupports(statement, release) {
137
+ if (statement.alternative_name !== void 0) return false;
138
+ if (statement.prefix !== void 0) return false;
139
+ if (statement.flags !== void 0) return false;
140
+ if (statement.version_added === false) return false;
141
+ const added = versionParts(statement.version_added);
142
+ const minimum = versionParts(release.minimum);
143
+ if (added === void 0 || minimum === void 0) return false;
144
+ if (compareVersions(minimum, added) < 0) return false;
145
+ if (statement.version_removed === void 0) return true;
146
+ const removed = versionParts(statement.version_removed);
147
+ const maximum = versionParts(release.maximum);
148
+ if (removed === void 0 || maximum === void 0) return true;
149
+ return compareVersions(maximum, removed) < 0;
150
+ }
151
+ function versionParts(version) {
152
+ const normalized = version.startsWith("≤") || version.startsWith("<=") ? version.replace(/^≤|^<=/, "") : version;
153
+ if (!/^\d+(?:\.\d+)*$/.test(normalized)) return void 0;
154
+ return normalized.split(".").map((part) => Number(part));
155
+ }
156
+ function compareVersions(left, right) {
157
+ const length = Math.max(left.length, right.length);
158
+ for (let index = 0; index < length; index += 1) {
159
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
160
+ if (difference !== 0) return difference;
161
+ }
162
+ return 0;
163
+ }
164
+
165
+ //#endregion
166
+ //#region src/infrastructure/browser-support/oxlint/index.ts
167
+ /** Oxlint plugin. Enable `browser-support/no-unsupported-api` in `jsPlugins`. */
168
+ const browserSupportPlugin = eslintCompatPlugin({
169
+ meta: { name: "browser-support" },
170
+ rules: { "no-unsupported-api": noUnsupportedApiRule }
171
+ });
172
+
173
+ //#endregion
174
+ export { browserSupportPlugin as default };
175
+ //# sourceMappingURL=oxlint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oxlint.js","names":[],"sources":["../../src/infrastructure/browser-support/oxlint/no-unsupported-api.ts","../../src/infrastructure/browser-support/oxlint/index.ts"],"sourcesContent":["import type {\n BrowserName,\n CompatStatement,\n Identifier,\n SimpleSupportStatement,\n SupportStatement,\n} from '@mdn/browser-compat-data/forLegacyNode'\nimport bcd from '@mdn/browser-compat-data/forLegacyNode'\nimport { defineRule } from '@oxlint/plugins'\nimport type { ESTree, Scope, SourceCode, Variable } from '@oxlint/plugins'\n\nimport type { BrowserFloorRelease } from '../floor.ts'\nimport { BrowserFloorError, browserFloor } from '../floor.ts'\nimport { POLYFILLED_APIS } from '../polyfills.ts'\n\nconst polyfilledApis = new Set<string>(POLYFILLED_APIS)\n\n/**\n * Reject a global static call that a browser in the floor does not implement.\n * `POLYFILLED_APIS` and a `// FALLBACK:` comment on the statement are allowed.\n * The floor is the nearest `.browserslistrc` from the linted file.\n */\nexport const noUnsupportedApiRule = defineRule({\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow a platform API that a browser in the browserslist floor does not implement.',\n },\n messages: {\n unsupported: '{{api}} is missing in {{browsers}}.',\n floor: 'The browserslist floor for {{path}} is {{reason}}.',\n },\n },\n createOnce(context) {\n const floors = new Map<string, readonly BrowserFloorRelease[] | BrowserFloorError>()\n const loadFloor = (path: string): readonly BrowserFloorRelease[] | BrowserFloorError => {\n const cached = floors.get(path)\n if (cached !== undefined) return cached\n try {\n const releases = browserFloor(path)\n floors.set(path, releases)\n return releases\n } catch (cause) {\n if (!(cause instanceof BrowserFloorError)) throw cause\n floors.set(path, cause)\n return cause\n }\n }\n\n return {\n Program(node) {\n const floor = loadFloor(context.filename)\n if (!(floor instanceof BrowserFloorError)) return\n context.report({\n node,\n messageId: 'floor',\n data: { path: context.filename, reason: floor.reason },\n })\n },\n MemberExpression(node) {\n const api = staticApi(node)\n if (api === undefined) return\n if (node.object.type !== 'Identifier') return\n if (!isPlatformReference(context.sourceCode, node.object)) return\n if (markedFallback(context.sourceCode, node)) return\n if (polyfilledApis.has(api)) return\n const floor = loadFloor(context.filename)\n if (floor instanceof BrowserFloorError) return\n const compat = compatFor(node.object.name, memberName(node))\n if (compat === undefined) return\n const browsers = unsupportedBrowsers(compat, floor)\n if (browsers.length === 0) return\n context.report({\n node,\n messageId: 'unsupported',\n data: { api, browsers: browsers.join(', ') },\n })\n },\n }\n },\n})\n\nfunction staticApi(node: ESTree.MemberExpression): string | undefined {\n if (node.computed || node.object.type !== 'Identifier' || node.property.type !== 'Identifier') {\n return undefined\n }\n return `${node.object.name}.${node.property.name}`\n}\n\nfunction memberName(node: ESTree.MemberExpression): string {\n if (node.property.type !== 'Identifier') return ''\n return node.property.name\n}\n\nfunction isPlatformReference(\n sourceCode: SourceCode,\n identifier: ESTree.IdentifierReference,\n): boolean {\n const variable = resolveVariable(sourceCode, identifier)\n if (variable === null || variable.defs.length === 0) return true\n return variable.defs.every((definition) => definition.type === 'ImplicitGlobalVariable')\n}\n\nfunction resolveVariable(\n sourceCode: SourceCode,\n identifier: ESTree.IdentifierReference,\n): Variable | null {\n let scope: Scope | null = sourceCode.getScope(identifier)\n while (scope !== null) {\n const variable = scope.set.get(identifier.name)\n if (variable !== undefined) return variable\n scope = scope.upper\n }\n return null\n}\n\nfunction markedFallback(sourceCode: SourceCode, node: ESTree.Node): boolean {\n let current: ESTree.Node | null = node\n while (current !== null && current.type !== 'Program') {\n const comments = [\n ...sourceCode.getCommentsBefore(current),\n ...sourceCode.getCommentsAfter(current),\n ]\n if (comments.some((comment) => comment.value.includes('FALLBACK:'))) return true\n current = current.parent\n }\n return false\n}\n\nfunction compatFor(objectName: string, name: string): CompatStatement | undefined {\n const builtins = bcd.javascript.builtins\n const fromJavascript = builtins === undefined ? undefined : lookup(builtins, objectName, name)\n return fromJavascript ?? lookup(bcd.api, objectName, name)\n}\n\n// BCD stores `URL.parse` as `parse_static`. The plain key is the instance or the builtin.\nfunction lookup(\n namespace: Identifier,\n objectName: string,\n name: string,\n): CompatStatement | undefined {\n const objectData = namespace[objectName]\n if (objectData === undefined) return undefined\n return objectData[`${name}_static`]?.__compat ?? objectData[name]?.__compat\n}\n\nfunction unsupportedBrowsers(\n compat: CompatStatement,\n floor: readonly BrowserFloorRelease[],\n): readonly string[] {\n const missing: string[] = []\n for (const release of floor) {\n const browser = compatBrowser(release.browser)\n if (browser === undefined || !rangeSupports(compat.support[browser], release)) {\n missing.push(`${release.browser} ${release.minimum}`)\n }\n }\n return missing\n}\n\nfunction compatBrowser(browser: string): BrowserName | undefined {\n switch (browser) {\n case 'chrome':\n case 'edge':\n case 'firefox':\n case 'safari':\n return browser\n case 'ios_saf':\n return 'safari_ios'\n default:\n return undefined\n }\n}\n\nfunction rangeSupports(\n support: SupportStatement | undefined,\n release: BrowserFloorRelease,\n): boolean {\n if (support === undefined) return false\n const statements = Array.isArray(support) ? support : [support]\n return statements.some((statement) => statementSupports(statement, release))\n}\n\n// An alternative_name or a prefix is a different API. A flag is off by default.\nfunction statementSupports(\n statement: SimpleSupportStatement,\n release: BrowserFloorRelease,\n): boolean {\n if (statement.alternative_name !== undefined) return false\n if (statement.prefix !== undefined) return false\n if (statement.flags !== undefined) return false\n if (statement.version_added === false) return false\n const added = versionParts(statement.version_added)\n const minimum = versionParts(release.minimum)\n if (added === undefined || minimum === undefined) return false\n if (compareVersions(minimum, added) < 0) return false\n if (statement.version_removed === undefined) return true\n const removed = versionParts(statement.version_removed)\n const maximum = versionParts(release.maximum)\n if (removed === undefined || maximum === undefined) return true\n return compareVersions(maximum, removed) < 0\n}\n\nfunction versionParts(version: string): readonly number[] | undefined {\n const normalized =\n version.startsWith('≤') || version.startsWith('<=') ? version.replace(/^≤|^<=/, '') : version\n if (!/^\\d+(?:\\.\\d+)*$/.test(normalized)) return undefined\n return normalized.split('.').map((part) => Number(part))\n}\n\nfunction compareVersions(left: readonly number[], right: readonly number[]): number {\n const length = Math.max(left.length, right.length)\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0)\n if (difference !== 0) return difference\n }\n return 0\n}\n","import { eslintCompatPlugin } from '@oxlint/plugins'\n\nimport { noUnsupportedApiRule } from './no-unsupported-api.ts'\n\n/** Oxlint plugin. Enable `browser-support/no-unsupported-api` in `jsPlugins`. */\nconst browserSupportPlugin = eslintCompatPlugin({\n meta: { name: 'browser-support' },\n rules: {\n 'no-unsupported-api': noUnsupportedApiRule,\n },\n})\n\nexport default browserSupportPlugin\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ //#region src/infrastructure/browser-support/polyfills.d.ts
2
+ /** Platform APIs `installPolyfills` provides. The lint rule allows these names. */
3
+ export declare const POLYFILLED_APIS: readonly ['URL.parse'];
4
+ /**
5
+ * Install `URL.parse` when the host has none.
6
+ * A native `URL.parse` on `URL` stays in place.
7
+ */
8
+ export declare function installPolyfills(): void;
9
+ //#endregion
10
+ //# sourceMappingURL=polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polyfills.d.ts","names":[],"sources":["../../src/infrastructure/browser-support/polyfills.ts"],"mappings":";;qBACa;;;;;wBAMG"}
@@ -0,0 +1,3 @@
1
+ import { n as installPolyfills, t as POLYFILLED_APIS } from "../polyfills-DEQFkqRI.js";
2
+
3
+ export { POLYFILLED_APIS, installPolyfills };
@@ -0,0 +1,51 @@
1
+ //#region src/infrastructure/browser-support/update-screen.d.ts
2
+ /** Set on `documentElement` when the update screen is showing. Hydration checks this name. */
3
+ export declare const unsupportedBrowserAttribute = "data-unsupported-browser";
4
+ /** Copy and public URLs for the update screen tags in the document head. */
5
+ export type UpdateScreenHead = {
6
+ readonly title: string;
7
+ readonly body: string;
8
+ /** Public URL of `updateScreenStyle`. */
9
+ readonly styleHref: string;
10
+ /** Public URL of `updateScreenScript`. */
11
+ readonly scriptSrc: string;
12
+ };
13
+ /** Stylesheet link a root route places in `head().links`. */
14
+ export type UpdateScreenLink = {
15
+ readonly rel: 'stylesheet';
16
+ readonly href: string;
17
+ };
18
+ /** Deferred script a root route places in `head().scripts`. */
19
+ export type UpdateScreenScript = {
20
+ readonly src: string;
21
+ readonly defer: true;
22
+ readonly 'data-update-title': string;
23
+ readonly 'data-update-body': string;
24
+ };
25
+ /**
26
+ * `links` and `scripts` a root route spreads into `head()`.
27
+ * The arrays are mutable so they match the `head()` return type.
28
+ */
29
+ export type UpdateScreenHeadEntries = {
30
+ links: UpdateScreenLink[];
31
+ scripts: UpdateScreenScript[];
32
+ };
33
+ /**
34
+ * Stylesheet and deferred script entries for a root route `head()`.
35
+ * Spread `links` and `scripts` into `head()`. The router escapes the attribute values.
36
+ * `defer` keeps parsing unblocked. The script is classic ES5, so a browser below the floor can show the screen.
37
+ */
38
+ export declare function updateScreenHeadScript(head: UpdateScreenHead): UpdateScreenHeadEntries;
39
+ /** True when the update screen script marked this element. Skip hydration when it is true. */
40
+ export declare function isUnsupportedBrowser(root: {
41
+ readonly hasAttribute: (name: string) => boolean;
42
+ }): boolean;
43
+ /**
44
+ * Classic ES5 script. Serve it at `scriptSrc`.
45
+ * It shows the screen when `CSS.registerProperty` or `color-mix()` is missing, and does nothing at the floor.
46
+ */
47
+ export declare const updateScreenScript: string;
48
+ /** Classic CSS for the update screen. Serve it at `styleHref`. Tokens fall back to neutral grey and a system sans-serif. */
49
+ export declare const updateScreenStyle = "html[data-unsupported-browser],\nhtml[data-unsupported-browser] body {\n margin: 0;\n min-height: 100%;\n background: var(--band, #f5f5f5);\n}\nhtml[data-unsupported-browser] body > :not(#unsupported-browser) {\n display: none !important;\n}\n#unsupported-browser {\n box-sizing: border-box;\n min-height: 100vh;\n margin: 0;\n padding: 48px 24px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n text-align: center;\n background: var(--band, #f5f5f5);\n color: var(--foreground, #171717);\n font-family: var(--font-text, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n}\n.unsupported-browser-title {\n margin: 0 0 12px;\n max-width: 28rem;\n font-family: var(--font-brand, ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n font-size: 2rem;\n font-weight: 500;\n line-height: 1.2;\n}\n.unsupported-browser-body {\n margin: 0;\n max-width: 32rem;\n color: var(--muted-foreground, #525252);\n font-size: 1.0625rem;\n line-height: 1.5;\n}\n";
50
+ //#endregion
51
+ //# sourceMappingURL=update-screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-screen.d.ts","names":[],"sources":["../../src/infrastructure/browser-support/update-screen.ts"],"mappings":";;qBACa;;YAOD;WACD;WACA;;WAEA;;WAEA;;;YAIC;WACD;WACA;;;YAIC;WACD;WACA;WACA;WACA;;;;;;YAOC;EACV,OAAO;EACP,SAAS;;;;;;;wBAQK,uBAAuB,MAAM,mBAAmB;;wBAehD,qBAAqB;WAC1B,eAAe;;;;;;qBASb;;qBAiCA"}
@@ -0,0 +1,107 @@
1
+ //#region src/infrastructure/browser-support/update-screen.ts
2
+ /** Set on `documentElement` when the update screen is showing. Hydration checks this name. */
3
+ const unsupportedBrowserAttribute = "data-unsupported-browser";
4
+ const updateTitleAttribute = "data-update-title";
5
+ const updateBodyAttribute = "data-update-body";
6
+ const fallbackFont = "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif";
7
+ /**
8
+ * Stylesheet and deferred script entries for a root route `head()`.
9
+ * Spread `links` and `scripts` into `head()`. The router escapes the attribute values.
10
+ * `defer` keeps parsing unblocked. The script is classic ES5, so a browser below the floor can show the screen.
11
+ */
12
+ function updateScreenHeadScript(head) {
13
+ return {
14
+ links: [{
15
+ rel: "stylesheet",
16
+ href: head.styleHref
17
+ }],
18
+ scripts: [{
19
+ src: head.scriptSrc,
20
+ defer: true,
21
+ [updateTitleAttribute]: head.title,
22
+ [updateBodyAttribute]: head.body
23
+ }]
24
+ };
25
+ }
26
+ /** True when the update screen script marked this element. Skip hydration when it is true. */
27
+ function isUnsupportedBrowser(root) {
28
+ return root.hasAttribute(unsupportedBrowserAttribute);
29
+ }
30
+ /**
31
+ * Classic ES5 script. Serve it at `scriptSrc`.
32
+ * It shows the screen when `CSS.registerProperty` or `color-mix()` is missing, and does nothing at the floor.
33
+ */
34
+ const updateScreenScript = `(function () {
35
+ var cssApi = typeof CSS === 'undefined' ? null : CSS;
36
+ var hasProperty = cssApi !== null && typeof cssApi.registerProperty === 'function';
37
+ var hasMix = cssApi !== null && typeof cssApi.supports === 'function' && cssApi.supports('color', 'color-mix(in srgb, red, blue)');
38
+ if (hasProperty && hasMix) return;
39
+ document.documentElement.setAttribute('${unsupportedBrowserAttribute}', '');
40
+ var scripts = document.getElementsByTagName('script');
41
+ var title = 'Update your browser';
42
+ var body = '';
43
+ for (var index = 0; index < scripts.length; index = index + 1) {
44
+ var marker = scripts[index].getAttribute('${updateTitleAttribute}');
45
+ if (marker === null) continue;
46
+ title = marker;
47
+ var text = scripts[index].getAttribute('${updateBodyAttribute}');
48
+ body = text === null ? '' : text;
49
+ break;
50
+ }
51
+ var screen = document.createElement('div');
52
+ screen.id = 'unsupported-browser';
53
+ var heading = document.createElement('h1');
54
+ heading.className = 'unsupported-browser-title';
55
+ heading.textContent = title;
56
+ var paragraph = document.createElement('p');
57
+ paragraph.className = 'unsupported-browser-body';
58
+ paragraph.textContent = body;
59
+ screen.appendChild(heading);
60
+ screen.appendChild(paragraph);
61
+ document.body.appendChild(screen);
62
+ })();
63
+ `;
64
+ /** Classic CSS for the update screen. Serve it at `styleHref`. Tokens fall back to neutral grey and a system sans-serif. */
65
+ const updateScreenStyle = `html[${unsupportedBrowserAttribute}],
66
+ html[${unsupportedBrowserAttribute}] body {
67
+ margin: 0;
68
+ min-height: 100%;
69
+ background: var(--band, #f5f5f5);
70
+ }
71
+ html[${unsupportedBrowserAttribute}] body > :not(#unsupported-browser) {
72
+ display: none !important;
73
+ }
74
+ #unsupported-browser {
75
+ box-sizing: border-box;
76
+ min-height: 100vh;
77
+ margin: 0;
78
+ padding: 48px 24px;
79
+ display: flex;
80
+ flex-direction: column;
81
+ justify-content: center;
82
+ align-items: center;
83
+ text-align: center;
84
+ background: var(--band, #f5f5f5);
85
+ color: var(--foreground, #171717);
86
+ font-family: var(--font-text, ${fallbackFont});
87
+ }
88
+ .unsupported-browser-title {
89
+ margin: 0 0 12px;
90
+ max-width: 28rem;
91
+ font-family: var(--font-brand, ${fallbackFont});
92
+ font-size: 2rem;
93
+ font-weight: 500;
94
+ line-height: 1.2;
95
+ }
96
+ .unsupported-browser-body {
97
+ margin: 0;
98
+ max-width: 32rem;
99
+ color: var(--muted-foreground, #525252);
100
+ font-size: 1.0625rem;
101
+ line-height: 1.5;
102
+ }
103
+ `;
104
+
105
+ //#endregion
106
+ export { isUnsupportedBrowser, unsupportedBrowserAttribute, updateScreenHeadScript, updateScreenScript, updateScreenStyle };
107
+ //# sourceMappingURL=update-screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-screen.js","names":[],"sources":["../../src/infrastructure/browser-support/update-screen.ts"],"sourcesContent":["/** Set on `documentElement` when the update screen is showing. Hydration checks this name. */\nexport const unsupportedBrowserAttribute = 'data-unsupported-browser'\n\nconst updateTitleAttribute = 'data-update-title' as const\nconst updateBodyAttribute = 'data-update-body' as const\nconst fallbackFont = 'ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif'\n\n/** Copy and public URLs for the update screen tags in the document head. */\nexport type UpdateScreenHead = {\n readonly title: string\n readonly body: string\n /** Public URL of `updateScreenStyle`. */\n readonly styleHref: string\n /** Public URL of `updateScreenScript`. */\n readonly scriptSrc: string\n}\n\n/** Stylesheet link a root route places in `head().links`. */\nexport type UpdateScreenLink = {\n readonly rel: 'stylesheet'\n readonly href: string\n}\n\n/** Deferred script a root route places in `head().scripts`. */\nexport type UpdateScreenScript = {\n readonly src: string\n readonly defer: true\n readonly 'data-update-title': string\n readonly 'data-update-body': string\n}\n\n/**\n * `links` and `scripts` a root route spreads into `head()`.\n * The arrays are mutable so they match the `head()` return type.\n */\nexport type UpdateScreenHeadEntries = {\n links: UpdateScreenLink[]\n scripts: UpdateScreenScript[]\n}\n\n/**\n * Stylesheet and deferred script entries for a root route `head()`.\n * Spread `links` and `scripts` into `head()`. The router escapes the attribute values.\n * `defer` keeps parsing unblocked. The script is classic ES5, so a browser below the floor can show the screen.\n */\nexport function updateScreenHeadScript(head: UpdateScreenHead): UpdateScreenHeadEntries {\n return {\n links: [{ rel: 'stylesheet', href: head.styleHref }],\n scripts: [\n {\n src: head.scriptSrc,\n defer: true,\n [updateTitleAttribute]: head.title,\n [updateBodyAttribute]: head.body,\n },\n ],\n }\n}\n\n/** True when the update screen script marked this element. Skip hydration when it is true. */\nexport function isUnsupportedBrowser(root: {\n readonly hasAttribute: (name: string) => boolean\n}): boolean {\n return root.hasAttribute(unsupportedBrowserAttribute)\n}\n\n/**\n * Classic ES5 script. Serve it at `scriptSrc`.\n * It shows the screen when `CSS.registerProperty` or `color-mix()` is missing, and does nothing at the floor.\n */\nexport const updateScreenScript = `(function () {\n var cssApi = typeof CSS === 'undefined' ? null : CSS;\n var hasProperty = cssApi !== null && typeof cssApi.registerProperty === 'function';\n var hasMix = cssApi !== null && typeof cssApi.supports === 'function' && cssApi.supports('color', 'color-mix(in srgb, red, blue)');\n if (hasProperty && hasMix) return;\n document.documentElement.setAttribute('${unsupportedBrowserAttribute}', '');\n var scripts = document.getElementsByTagName('script');\n var title = 'Update your browser';\n var body = '';\n for (var index = 0; index < scripts.length; index = index + 1) {\n var marker = scripts[index].getAttribute('${updateTitleAttribute}');\n if (marker === null) continue;\n title = marker;\n var text = scripts[index].getAttribute('${updateBodyAttribute}');\n body = text === null ? '' : text;\n break;\n }\n var screen = document.createElement('div');\n screen.id = 'unsupported-browser';\n var heading = document.createElement('h1');\n heading.className = 'unsupported-browser-title';\n heading.textContent = title;\n var paragraph = document.createElement('p');\n paragraph.className = 'unsupported-browser-body';\n paragraph.textContent = body;\n screen.appendChild(heading);\n screen.appendChild(paragraph);\n document.body.appendChild(screen);\n})();\n`\n\n// `!important` keeps application styles from painting over the update screen.\n/** Classic CSS for the update screen. Serve it at `styleHref`. Tokens fall back to neutral grey and a system sans-serif. */\nexport const updateScreenStyle = `html[${unsupportedBrowserAttribute}],\nhtml[${unsupportedBrowserAttribute}] body {\n margin: 0;\n min-height: 100%;\n background: var(--band, #f5f5f5);\n}\nhtml[${unsupportedBrowserAttribute}] body > :not(#unsupported-browser) {\n display: none !important;\n}\n#unsupported-browser {\n box-sizing: border-box;\n min-height: 100vh;\n margin: 0;\n padding: 48px 24px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n text-align: center;\n background: var(--band, #f5f5f5);\n color: var(--foreground, #171717);\n font-family: var(--font-text, ${fallbackFont});\n}\n.unsupported-browser-title {\n margin: 0 0 12px;\n max-width: 28rem;\n font-family: var(--font-brand, ${fallbackFont});\n font-size: 2rem;\n font-weight: 500;\n line-height: 1.2;\n}\n.unsupported-browser-body {\n margin: 0;\n max-width: 32rem;\n color: var(--muted-foreground, #525252);\n font-size: 1.0625rem;\n line-height: 1.5;\n}\n`\n"],"mappings":";;AACA,MAAa,8BAA8B;AAE3C,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,eAAe;;;;;;AAwCrB,SAAgB,uBAAuB,MAAiD;CACtF,OAAO;EACL,OAAO,CAAC;GAAE,KAAK;GAAc,MAAM,KAAK;EAAU,CAAC;EACnD,SAAS,CACP;GACE,KAAK,KAAK;GACV,OAAO;IACN,uBAAuB,KAAK;IAC5B,sBAAsB,KAAK;EAC9B,CACF;CACF;AACF;;AAGA,SAAgB,qBAAqB,MAEzB;CACV,OAAO,KAAK,aAAa,2BAA2B;AACtD;;;;;AAMA,MAAa,qBAAqB;;;;;2CAKS,4BAA4B;;;;;gDAKvB,qBAAqB;;;8CAGvB,oBAAoB;;;;;;;;;;;;;;;;;;AAoBlE,MAAa,oBAAoB,QAAQ,4BAA4B;OAC9D,4BAA4B;;;;;OAK5B,4BAA4B;;;;;;;;;;;;;;;kCAeD,aAAa;;;;;mCAKZ,aAAa"}
@@ -0,0 +1,146 @@
1
+ import { nt as AppError } from "./core-BiN6S_cm.js";
2
+ import browserslist from "browserslist";
3
+
4
+ //#region src/infrastructure/browser-support/browser-floor.error.ts
5
+ /** The browserslist file is missing, empty, or not a query browserslist can resolve. */
6
+ var BrowserFloorError = class extends AppError {
7
+ path;
8
+ reason;
9
+ _tag = "BrowserFloorError";
10
+ classification = "terminal";
11
+ /**
12
+ * `path` is the file or directory passed to `browserFloor`.
13
+ * `reason` is `missing` when no config is found, `empty` when the query matches no browsers,
14
+ * and `invalid` when browserslist rejects the query. `cause` is that rejection.
15
+ */
16
+ constructor(path, reason, cause) {
17
+ super(floorMessage(reason, path), { cause });
18
+ this.path = path;
19
+ this.reason = reason;
20
+ }
21
+ };
22
+ function floorMessage(reason, path) {
23
+ switch (reason) {
24
+ case "missing": return `No browserslist config at ${path}`;
25
+ case "empty": return `The browserslist config at ${path} matches no browsers`;
26
+ case "invalid": return `The browserslist config at ${path} is invalid`;
27
+ }
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/infrastructure/browser-support/floor.ts
32
+ /**
33
+ * Resolve the minimum and maximum version of each browser in the browserslist config.
34
+ * The path is a config file or any file inside the project that owns `.browserslistrc`.
35
+ * Throws `BrowserFloorError` when the config is missing, empty, or invalid.
36
+ */
37
+ function browserFloor(path) {
38
+ const queries = loadQueries(path);
39
+ if (queries === void 0) throw new BrowserFloorError(path, "missing");
40
+ if (queries.length === 0) throw new BrowserFloorError(path, "empty");
41
+ let resolved;
42
+ try {
43
+ resolved = browserslist(queries, { path });
44
+ } catch (cause) {
45
+ throw new BrowserFloorError(path, "invalid", cause);
46
+ }
47
+ const releases = floorReleases(resolved);
48
+ if (releases.length === 0) throw new BrowserFloorError(path, "empty");
49
+ return releases;
50
+ }
51
+ /**
52
+ * Vite `environments.client.build.target` values for the browserslist config at `path`.
53
+ * Throws `BrowserFloorError` when the config is missing, empty, or invalid.
54
+ */
55
+ function buildTargets(path) {
56
+ return browserFloor(path).map((release) => `${viteBrowser(release.browser)}${release.minimum}`);
57
+ }
58
+ /** Sentence that names the floor versions. Pass the result of `browserFloor`. */
59
+ function updateScreenCopy(releases) {
60
+ return {
61
+ title: "Update your browser",
62
+ body: `This application needs ${releases.map((release) => `${browserLabel(release.browser)} ${release.minimum}`).join(", ")}.`
63
+ };
64
+ }
65
+ function loadQueries(path) {
66
+ try {
67
+ return browserslist.loadConfig({ path });
68
+ } catch (cause) {
69
+ throw new BrowserFloorError(path, "invalid", cause);
70
+ }
71
+ }
72
+ function floorReleases(resolved) {
73
+ const ranges = /* @__PURE__ */ new Map();
74
+ for (const entry of resolved) {
75
+ const release = splitRelease(entry);
76
+ if (release === void 0) continue;
77
+ const current = ranges.get(release.browser);
78
+ if (current === void 0) {
79
+ ranges.set(release.browser, {
80
+ minimum: release.version,
81
+ maximum: release.version
82
+ });
83
+ continue;
84
+ }
85
+ if (compareVersionText(release.version, current.minimum) < 0) current.minimum = release.version;
86
+ if (compareVersionText(release.version, current.maximum) > 0) current.maximum = release.version;
87
+ }
88
+ return [...ranges.entries()].map(([browser, range]) => ({
89
+ browser,
90
+ minimum: range.minimum,
91
+ maximum: range.maximum
92
+ })).sort((left, right) => compareText(left.browser, right.browser));
93
+ }
94
+ function splitRelease(entry) {
95
+ const space = entry.indexOf(" ");
96
+ if (space === -1) return void 0;
97
+ const browser = entry.slice(0, space);
98
+ const version = entry.slice(space + 1);
99
+ if (versionParts(version) === void 0) return void 0;
100
+ return {
101
+ browser,
102
+ version
103
+ };
104
+ }
105
+ function compareVersionText(left, right) {
106
+ const leftParts = versionParts(left);
107
+ const rightParts = versionParts(right);
108
+ if (leftParts === void 0 || rightParts === void 0) return 0;
109
+ return compareVersions(leftParts, rightParts);
110
+ }
111
+ function versionParts(version) {
112
+ const normalized = version.startsWith("≤") || version.startsWith("<=") ? version.replace(/^≤|^<=/, "") : version;
113
+ if (!/^\d+(?:\.\d+)*$/.test(normalized)) return void 0;
114
+ return normalized.split(".").map((part) => Number(part));
115
+ }
116
+ function compareVersions(left, right) {
117
+ const length = Math.max(left.length, right.length);
118
+ for (let index = 0; index < length; index += 1) {
119
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
120
+ if (difference !== 0) return difference;
121
+ }
122
+ return 0;
123
+ }
124
+ function compareText(left, right) {
125
+ if (left < right) return -1;
126
+ if (left > right) return 1;
127
+ return 0;
128
+ }
129
+ function viteBrowser(browser) {
130
+ if (browser === "ios_saf") return "ios";
131
+ return browser;
132
+ }
133
+ function browserLabel(browser) {
134
+ switch (browser) {
135
+ case "chrome": return "Chrome";
136
+ case "edge": return "Edge";
137
+ case "firefox": return "Firefox";
138
+ case "safari": return "Safari";
139
+ case "ios_saf": return "iOS";
140
+ default: return browser;
141
+ }
142
+ }
143
+
144
+ //#endregion
145
+ export { BrowserFloorError as i, buildTargets as n, updateScreenCopy as r, browserFloor as t };
146
+ //# sourceMappingURL=floor-Cuw8EIhg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floor-Cuw8EIhg.js","names":[],"sources":["../src/infrastructure/browser-support/browser-floor.error.ts","../src/infrastructure/browser-support/floor.ts"],"sourcesContent":["import { AppError } from '@/core'\n\n/** The browserslist file is missing, empty, or not a query browserslist can resolve. */\nexport class BrowserFloorError extends AppError {\n readonly _tag = 'BrowserFloorError'\n readonly classification = 'terminal'\n\n /**\n * `path` is the file or directory passed to `browserFloor`.\n * `reason` is `missing` when no config is found, `empty` when the query matches no browsers,\n * and `invalid` when browserslist rejects the query. `cause` is that rejection.\n */\n constructor(\n readonly path: string,\n readonly reason: 'missing' | 'empty' | 'invalid',\n cause?: unknown,\n ) {\n super(floorMessage(reason, path), { cause })\n }\n}\n\nfunction floorMessage(reason: 'missing' | 'empty' | 'invalid', path: string): string {\n switch (reason) {\n case 'missing':\n return `No browserslist config at ${path}`\n case 'empty':\n return `The browserslist config at ${path} matches no browsers`\n case 'invalid':\n return `The browserslist config at ${path} is invalid`\n }\n}\n","import browserslist from 'browserslist'\n\nimport { BrowserFloorError } from './browser-floor.error.ts'\n\nexport { BrowserFloorError }\n\n/** Oldest and newest version of one browser that the floor still includes. */\nexport type BrowserFloorRelease = {\n readonly browser: string\n readonly minimum: string\n readonly maximum: string\n}\n\n/** Title and body for the update screen, taken from the floor versions. */\nexport type UpdateScreenCopy = {\n readonly title: string\n readonly body: string\n}\n\n/**\n * Resolve the minimum and maximum version of each browser in the browserslist config.\n * The path is a config file or any file inside the project that owns `.browserslistrc`.\n * Throws `BrowserFloorError` when the config is missing, empty, or invalid.\n */\nexport function browserFloor(path: string): readonly BrowserFloorRelease[] {\n const queries = loadQueries(path)\n if (queries === undefined) throw new BrowserFloorError(path, 'missing')\n if (queries.length === 0) throw new BrowserFloorError(path, 'empty')\n let resolved: readonly string[]\n try {\n resolved = browserslist(queries, { path })\n } catch (cause) {\n throw new BrowserFloorError(path, 'invalid', cause)\n }\n const releases = floorReleases(resolved)\n if (releases.length === 0) throw new BrowserFloorError(path, 'empty')\n return releases\n}\n\n/**\n * Vite `environments.client.build.target` values for the browserslist config at `path`.\n * Throws `BrowserFloorError` when the config is missing, empty, or invalid.\n */\nexport function buildTargets(path: string): readonly string[] {\n return browserFloor(path).map((release) => `${viteBrowser(release.browser)}${release.minimum}`)\n}\n\n/** Sentence that names the floor versions. Pass the result of `browserFloor`. */\nexport function updateScreenCopy(releases: readonly BrowserFloorRelease[]): UpdateScreenCopy {\n const versions = releases\n .map((release) => `${browserLabel(release.browser)} ${release.minimum}`)\n .join(', ')\n return {\n title: 'Update your browser',\n body: `This application needs ${versions}.`,\n }\n}\n\nfunction loadQueries(path: string): readonly string[] | undefined {\n try {\n return browserslist.loadConfig({ path })\n } catch (cause) {\n throw new BrowserFloorError(path, 'invalid', cause)\n }\n}\n\nfunction floorReleases(resolved: readonly string[]): BrowserFloorRelease[] {\n const ranges = new Map<string, { minimum: string; maximum: string }>()\n for (const entry of resolved) {\n const release = splitRelease(entry)\n if (release === undefined) continue\n const current = ranges.get(release.browser)\n if (current === undefined) {\n ranges.set(release.browser, { minimum: release.version, maximum: release.version })\n continue\n }\n if (compareVersionText(release.version, current.minimum) < 0) current.minimum = release.version\n if (compareVersionText(release.version, current.maximum) > 0) current.maximum = release.version\n }\n return [...ranges.entries()]\n .map(([browser, range]) => ({ browser, minimum: range.minimum, maximum: range.maximum }))\n .sort((left, right) => compareText(left.browser, right.browser))\n}\n\nfunction splitRelease(\n entry: string,\n): { readonly browser: string; readonly version: string } | undefined {\n const space = entry.indexOf(' ')\n if (space === -1) return undefined\n const browser = entry.slice(0, space)\n const version = entry.slice(space + 1)\n if (versionParts(version) === undefined) return undefined\n return { browser, version }\n}\n\nfunction compareVersionText(left: string, right: string): number {\n const leftParts = versionParts(left)\n const rightParts = versionParts(right)\n if (leftParts === undefined || rightParts === undefined) return 0\n return compareVersions(leftParts, rightParts)\n}\n\nfunction versionParts(version: string): readonly number[] | undefined {\n const normalized =\n version.startsWith('≤') || version.startsWith('<=') ? version.replace(/^≤|^<=/, '') : version\n if (!/^\\d+(?:\\.\\d+)*$/.test(normalized)) return undefined\n return normalized.split('.').map((part) => Number(part))\n}\n\nfunction compareVersions(left: readonly number[], right: readonly number[]): number {\n const length = Math.max(left.length, right.length)\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0)\n if (difference !== 0) return difference\n }\n return 0\n}\n\nfunction compareText(left: string, right: string): number {\n if (left < right) return -1\n if (left > right) return 1\n return 0\n}\n\nfunction viteBrowser(browser: string): string {\n if (browser === 'ios_saf') return 'ios'\n return browser\n}\n\nfunction browserLabel(browser: string): string {\n switch (browser) {\n case 'chrome':\n return 'Chrome'\n case 'edge':\n return 'Edge'\n case 'firefox':\n return 'Firefox'\n case 'safari':\n return 'Safari'\n case 'ios_saf':\n return 'iOS'\n default:\n return browser\n }\n}\n"],"mappings":"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"}