tempest-react-sdk 0.8.0 → 0.9.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/vite.js CHANGED
@@ -1,46 +1,265 @@
1
- import { resolve as g } from "node:path";
2
- import { defineConfig as u } from "vite";
3
- import v from "@vitejs/plugin-react";
4
- function m(t) {
5
- const r = {};
6
- for (const [n, e] of Object.entries(t))
7
- r[n] = typeof e == "string" ? { target: e, changeOrigin: !0 } : e;
8
- return r;
9
- }
10
- function h(t = {}) {
1
+ import { resolve as k } from "node:path";
2
+ import { defineConfig as z } from "vite";
3
+ import B from "@vitejs/plugin-react";
4
+ import { readFile as P } from "node:fs/promises";
5
+ function H(t) {
6
+ const e = {};
7
+ for (const [o, a] of Object.entries(t))
8
+ e[o] = typeof a == "string" ? { target: a, changeOrigin: !0 } : a;
9
+ return e;
10
+ }
11
+ function N(t = {}) {
11
12
  const {
12
- srcDir: r = "src",
13
- port: n = 5173,
14
- host: e = "127.0.0.1",
15
- open: c = !1,
16
- proxy: i,
17
- alias: p = {},
18
- plugins: l = [],
19
- overrides: a = {}
20
- } = t, o = a, s = {
21
- plugins: [v(), ...l],
13
+ srcDir: e = "src",
14
+ port: o = 5173,
15
+ host: a = "127.0.0.1",
16
+ open: h = !1,
17
+ proxy: s,
18
+ alias: d = {},
19
+ plugins: f = [],
20
+ overrides: u = {}
21
+ } = t, r = u, c = {
22
+ plugins: [B(), ...f],
22
23
  resolve: {
23
24
  alias: {
24
- "@": g(process.cwd(), r),
25
- ...p
25
+ "@": k(process.cwd(), e),
26
+ ...d
26
27
  }
27
28
  },
28
29
  server: {
29
- port: n,
30
- host: e,
31
- open: c,
32
- ...i ? { proxy: m(i) } : {}
30
+ port: o,
31
+ host: a,
32
+ open: h,
33
+ ...s ? { proxy: H(s) } : {}
34
+ }
35
+ }, l = {
36
+ ...c,
37
+ ...r,
38
+ plugins: [...c.plugins ?? [], ...r.plugins ?? []],
39
+ resolve: { ...c.resolve, ...r.resolve },
40
+ server: { ...c.server, ...r.server }
41
+ };
42
+ return z(l);
43
+ }
44
+ function I(t, e) {
45
+ return `${t.endsWith("/") ? t : `${t}/`}${e}`.replace(/([^:]\/)\/+/g, "$1");
46
+ }
47
+ function A(t) {
48
+ let e = 5381;
49
+ for (let o = 0; o < t.length; o++)
50
+ e = (e << 5) + e + t.charCodeAt(o) >>> 0;
51
+ return e.toString(16);
52
+ }
53
+ function U(t = {}) {
54
+ const {
55
+ fileName: e = "precache-manifest.json",
56
+ additionalUrls: o = [],
57
+ exclude: a = /\.map$/,
58
+ includeHtml: h = !0,
59
+ appShell: s = "/index.html"
60
+ } = t;
61
+ let d = "/";
62
+ return {
63
+ name: "tempest-pwa-manifest",
64
+ apply: "build",
65
+ configResolved(u) {
66
+ d = u.base ?? "/";
67
+ },
68
+ generateBundle(u, r) {
69
+ const c = new Set(o);
70
+ s && c.add(s);
71
+ for (const m of Object.keys(r))
72
+ m !== e && (a.test(m) || !h && m.endsWith(".html") || c.add(I(d, m)));
73
+ const l = [...c].sort(), g = A(l.join(`
74
+ `));
75
+ this.emitFile({
76
+ type: "asset",
77
+ fileName: e,
78
+ source: JSON.stringify({ version: g, urls: l })
79
+ });
80
+ }
81
+ };
82
+ }
83
+ const D = [
84
+ { width: 375, height: 667, ratio: 2 },
85
+ // iPhone SE / 8
86
+ { width: 375, height: 812, ratio: 3 },
87
+ // iPhone X / 11 Pro
88
+ { width: 390, height: 844, ratio: 3 },
89
+ // iPhone 12 / 13 / 14
90
+ { width: 393, height: 852, ratio: 3 },
91
+ // iPhone 14 Pro / 15
92
+ { width: 414, height: 896, ratio: 2 },
93
+ // iPhone XR / 11
94
+ { width: 414, height: 896, ratio: 3 },
95
+ // iPhone XS Max / 11 Pro Max
96
+ { width: 428, height: 926, ratio: 3 },
97
+ // iPhone 13/14 Pro Max
98
+ { width: 430, height: 932, ratio: 3 },
99
+ // iPhone 15 Pro Max
100
+ { width: 768, height: 1024, ratio: 2 },
101
+ // iPad
102
+ { width: 834, height: 1194, ratio: 2 },
103
+ // iPad Pro 11"
104
+ { width: 1024, height: 1366, ratio: 2 }
105
+ // iPad Pro 12.9"
106
+ ];
107
+ function $(t) {
108
+ return `splash/apple-splash-${t.width * t.ratio}x${t.height * t.ratio}.png`;
109
+ }
110
+ function F(t) {
111
+ return `(device-width: ${t.width}px) and (device-height: ${t.height}px) and (-webkit-device-pixel-ratio: ${t.ratio}) and (orientation: portrait)`;
112
+ }
113
+ function M(t) {
114
+ const e = t.replace("#", ""), o = e.length === 3 ? e.split("").map((a) => a + a).join("") : e;
115
+ return {
116
+ r: parseInt(o.slice(0, 2), 16),
117
+ g: parseInt(o.slice(2, 4), 16),
118
+ b: parseInt(o.slice(4, 6), 16)
119
+ };
120
+ }
121
+ function L(t = {}) {
122
+ const {
123
+ source: e = "public/icon.svg",
124
+ sizes: o = [192, 512],
125
+ maskableSizes: a = [512],
126
+ appleTouchIcon: h = 180,
127
+ outDir: s = "icons",
128
+ background: d = "#ffffff",
129
+ maskablePadding: f = 0.1,
130
+ appleSplash: u = !1,
131
+ splashBackground: r,
132
+ splashIconScale: c = 0.3
133
+ } = t, l = u ? Array.isArray(u) ? u : D : [];
134
+ let g = process.cwd();
135
+ return {
136
+ name: "tempest-pwa-icons",
137
+ apply: "build",
138
+ configResolved(p) {
139
+ g = p.root ?? process.cwd();
140
+ },
141
+ transformIndexHtml() {
142
+ if (l.length)
143
+ return l.map((p) => ({
144
+ tag: "link",
145
+ attrs: {
146
+ rel: "apple-touch-startup-image",
147
+ media: F(p),
148
+ href: `/${$(p)}`
149
+ },
150
+ injectTo: "head"
151
+ }));
152
+ },
153
+ async generateBundle() {
154
+ let p;
155
+ try {
156
+ const i = await import("sharp");
157
+ p = i.default ?? i;
158
+ } catch {
159
+ this.warn(
160
+ "tempestPwaIcons: `sharp` is not installed — skipping icon generation. Run `npm i -D sharp` to enable it."
161
+ );
162
+ return;
163
+ }
164
+ const w = await P(k(g, e)), y = M(d), b = (n, i) => {
165
+ this.emitFile({ type: "asset", fileName: n, source: i });
166
+ };
167
+ for (const n of o) {
168
+ const i = await p(w, { density: Math.max(n, 512) }).resize(n, n, {
169
+ fit: "contain",
170
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
171
+ }).png().toBuffer();
172
+ b(`${s}/icon-${n}.png`, i);
173
+ }
174
+ for (const n of a) {
175
+ const i = Math.round(n * (1 - f * 2)), v = await p(w, { density: Math.max(n, 512) }).resize(i, i, {
176
+ fit: "contain",
177
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
178
+ }).extend({
179
+ top: Math.round((n - i) / 2),
180
+ bottom: Math.round((n - i) / 2),
181
+ left: Math.round((n - i) / 2),
182
+ right: Math.round((n - i) / 2),
183
+ background: { ...y, alpha: 1 }
184
+ }).resize(n, n).png().toBuffer();
185
+ b(`${s}/maskable-${n}.png`, v);
186
+ }
187
+ if (h) {
188
+ const n = await p(w, { density: Math.max(h, 512) }).resize(h, h, {
189
+ fit: "contain",
190
+ background: { ...y, alpha: 1 }
191
+ }).flatten({ background: y }).png().toBuffer();
192
+ b("apple-touch-icon.png", n);
193
+ }
194
+ if (l.length) {
195
+ const n = M(r ?? d);
196
+ for (const i of l) {
197
+ const v = i.width * i.ratio, S = i.height * i.ratio, x = Math.round(Math.min(v, S) * c), j = await p(w, { density: Math.max(x, 512) }).resize(x, x, {
198
+ fit: "contain",
199
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
200
+ }).png().toBuffer(), C = await p({
201
+ create: {
202
+ width: v,
203
+ height: S,
204
+ channels: 4,
205
+ background: { ...n, alpha: 1 }
206
+ }
207
+ }).composite([{ input: j, gravity: "center" }]).png().toBuffer();
208
+ b($(i), C);
209
+ }
210
+ }
211
+ }
212
+ };
213
+ }
214
+ function J(t = {}) {
215
+ const {
216
+ swSrc: e = "src/sw.ts",
217
+ swUrl: o = "/sw.js",
218
+ manifestUrl: a = "/precache-manifest.json",
219
+ enabled: h = !0
220
+ } = t;
221
+ let s = process.cwd();
222
+ return {
223
+ name: "tempest-pwa-dev-sw",
224
+ apply: "serve",
225
+ configResolved(f) {
226
+ s = f.root ?? process.cwd();
227
+ },
228
+ configureServer(f) {
229
+ h && f.middlewares.use(async (u, r, c) => {
230
+ const l = (u.url ?? "").split("?")[0];
231
+ if (l === o) {
232
+ try {
233
+ const m = await (await import("esbuild")).build({
234
+ entryPoints: [k(s, e)],
235
+ bundle: !0,
236
+ format: "iife",
237
+ platform: "browser",
238
+ target: "es2020",
239
+ write: !1,
240
+ absWorkingDir: s,
241
+ logLevel: "silent"
242
+ });
243
+ r.setHeader("Content-Type", "application/javascript"), r.setHeader("Service-Worker-Allowed", "/"), r.setHeader("Cache-Control", "no-cache"), r.end(m.outputFiles[0].text);
244
+ } catch (g) {
245
+ r.statusCode = 500, r.end(`// SW dev build failed:
246
+ // ${String(g)}`);
247
+ }
248
+ return;
249
+ }
250
+ if (l === a) {
251
+ r.setHeader("Content-Type", "application/json"), r.setHeader("Cache-Control", "no-cache"), r.end(JSON.stringify({ version: "dev", urls: [] }));
252
+ return;
253
+ }
254
+ c();
255
+ });
33
256
  }
34
- }, f = {
35
- ...s,
36
- ...o,
37
- plugins: [...s.plugins ?? [], ...o.plugins ?? []],
38
- resolve: { ...s.resolve, ...o.resolve },
39
- server: { ...s.server, ...o.server }
40
257
  };
41
- return u(f);
42
258
  }
43
259
  export {
44
- h as createViteConfig
260
+ N as createViteConfig,
261
+ J as tempestPwaDevSw,
262
+ L as tempestPwaIcons,
263
+ U as tempestPwaManifest
45
264
  };
46
265
  //# sourceMappingURL=vite.js.map
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","sources":["../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n\n const base: UserConfig = {\n plugins: [react(), ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"names":["normalizeProxy","proxy","out","path","value","createViteConfig","options","srcDir","port","host","open","alias","plugins","overrides","overridesConfig","base","react","resolve","merged","defineConfig"],"mappings":";;;AAgDA,SAASA,EAAeC,GAAiE;AACrF,QAAMC,IAAoC,CAAA;AAC1C,aAAW,CAACC,GAAMC,CAAK,KAAK,OAAO,QAAQH,CAAK;AAC5C,IAAAC,EAAIC,CAAI,IACJ,OAAOC,KAAU,WACX,EAAE,QAAQA,GAAO,cAAc,GAAA,IAC9BA;AAEf,SAAOF;AACX;AAkBO,SAASG,EAAiBC,IAAmC,IAAuB;AACvF,QAAM;AAAA,IACF,QAAAC,IAAS;AAAA,IACT,MAAAC,IAAO;AAAA,IACP,MAAAC,IAAO;AAAA,IACP,MAAAC,IAAO;AAAA,IACP,OAAAT;AAAA,IACA,OAAAU,IAAQ,CAAA;AAAA,IACR,SAAAC,IAAU,CAAA;AAAA,IACV,WAAAC,IAAY,CAAA;AAAA,EAAC,IACbP,GAEEQ,IAAkBD,GAElBE,IAAmB;AAAA,IACrB,SAAS,CAACC,KAAS,GAAGJ,CAAO;AAAA,IAC7B,SAAS;AAAA,MACL,OAAO;AAAA,QACH,KAAKK,EAAQ,QAAQ,IAAA,GAAOV,CAAM;AAAA,QAClC,GAAGI;AAAA,MAAA;AAAA,IACP;AAAA,IAEJ,QAAQ;AAAA,MACJ,MAAAH;AAAA,MACA,MAAAC;AAAA,MACA,MAAAC;AAAA,MACA,GAAIT,IAAQ,EAAE,OAAOD,EAAeC,CAAK,EAAA,IAAM,CAAA;AAAA,IAAC;AAAA,EACpD,GAGEiB,IAAqB;AAAA,IACvB,GAAGH;AAAA,IACH,GAAGD;AAAA,IACH,SAAS,CAAC,GAAIC,EAAK,WAAW,CAAA,GAAK,GAAID,EAAgB,WAAW,EAAG;AAAA,IACrE,SAAS,EAAE,GAAGC,EAAK,SAAS,GAAGD,EAAgB,QAAA;AAAA,IAC/C,QAAQ,EAAE,GAAGC,EAAK,QAAQ,GAAGD,EAAgB,OAAA;AAAA,EAAO;AAGxD,SAAOK,EAAaD,CAAM;AAC9B;"}
1
+ {"version":3,"file":"vite.js","sources":["../src/vite/create-vite-config.ts","../src/vite/tempest-pwa-manifest.ts","../src/vite/tempest-pwa-icons.ts","../src/vite/tempest-pwa-dev-sw.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n\n const base: UserConfig = {\n plugins: [react(), ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n","import type { Plugin } from \"vite\";\n\n/**\n * A Vite plugin object. Typed loosely so the SDK's published declarations stay\n * free of `vite`'s internal types (which the `.d.ts` rollup can't analyze);\n * assign the result straight into a `plugins: [...]` array.\n */\nexport type TempestVitePlugin = { name: string } & Record<string, unknown>;\n\n/** Options for {@link tempestPwaManifest}. */\nexport interface TempestPwaManifestOptions {\n /** Output file name (under the build root). Default `precache-manifest.json`. */\n fileName?: string;\n /**\n * Extra URLs to precache that Vite doesn't emit into the bundle — typically\n * `public/` assets like the web manifest and icons. Default `[]`.\n */\n additionalUrls?: string[];\n /** Emitted files matching this are skipped. Default `/\\.map$/` (source maps). */\n exclude?: RegExp;\n /** Include emitted `.html` documents (the app shell). Default `true`. */\n includeHtml?: boolean;\n /**\n * App-shell document always added to the manifest, even if Vite emits it\n * after this plugin runs. Must match `installPrecache`'s `navigateFallback`\n * so offline navigations resolve. Pass `false` to disable. Default `/index.html`.\n */\n appShell?: string | false;\n}\n\nfunction joinBase(base: string, file: string): string {\n const prefix = base.endsWith(\"/\") ? base : `${base}/`;\n return `${prefix}${file}`.replace(/([^:]\\/)\\/+/g, \"$1\");\n}\n\n/** Deterministic djb2 hash → hex. Stable across rebuilds with the same assets. */\nfunction hash(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) >>> 0;\n }\n return h.toString(16);\n}\n\n/**\n * Vite build plugin that emits a `precache-manifest.json` listing every built\n * asset (plus any `additionalUrls`) as root-absolute URLs, with a content-based\n * `version`. It is the dependency-free counterpart to Workbox's `__WB_MANIFEST`:\n * `installPrecache` (from `tempest-react-sdk/sw`) reads this file at the service\n * worker's `install` event to cache the app shell for offline use.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestPwaManifest } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * plugins: [tempestPwaManifest({ additionalUrls: [\"/manifest.webmanifest\", \"/icon.svg\"] })],\n * });\n */\nexport function tempestPwaManifest(options: TempestPwaManifestOptions = {}): TempestVitePlugin {\n const {\n fileName = \"precache-manifest.json\",\n additionalUrls = [],\n exclude = /\\.map$/,\n includeHtml = true,\n appShell = \"/index.html\",\n } = options;\n\n let base = \"/\";\n\n const plugin: Plugin = {\n name: \"tempest-pwa-manifest\",\n apply: \"build\",\n configResolved(config) {\n base = config.base ?? \"/\";\n },\n generateBundle(_outputOptions, bundle) {\n const urls = new Set<string>(additionalUrls);\n // Vite may emit index.html after this hook, so guarantee the shell.\n if (appShell) urls.add(appShell);\n for (const file of Object.keys(bundle)) {\n if (file === fileName) continue;\n if (exclude.test(file)) continue;\n if (!includeHtml && file.endsWith(\".html\")) continue;\n urls.add(joinBase(base, file));\n }\n\n const list = [...urls].sort();\n const version = hash(list.join(\"\\n\"));\n this.emitFile({\n type: \"asset\",\n fileName,\n source: JSON.stringify({ version, urls: list }),\n });\n },\n };\n\n return plugin as TempestVitePlugin;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestPwaIcons}. */\nexport interface TempestPwaIconsOptions {\n /** Source image (SVG or large PNG), relative to the project root. Default `public/icon.svg`. */\n source?: string;\n /** Square \"any\"-purpose icon sizes to emit. Default `[192, 512]`. */\n sizes?: number[];\n /** Square \"maskable\" icon sizes to emit (with safe-zone padding). Default `[512]`. */\n maskableSizes?: number[];\n /** Apple touch icon size, or `false` to skip. Default `180`. */\n appleTouchIcon?: number | false;\n /** Output directory for the icon set, under the build root. Default `icons`. */\n outDir?: string;\n /** Opaque background for maskable + apple icons (no transparency allowed). Default `#ffffff`. */\n background?: string;\n /** Maskable safe-zone padding as a fraction of the icon. Default `0.1` (10% each side). */\n maskablePadding?: number;\n /**\n * Generate Apple splash screens (launch images) and inject the matching\n * `<link rel=\"apple-touch-startup-image\">` tags. `true` uses a built-in set\n * of common iPhone/iPad portrait sizes; pass an array to override. Default `false`.\n */\n appleSplash?: boolean | AppleSplashSpec[];\n /** Background color for splash screens. Default: `background`. */\n splashBackground?: string;\n /** Icon size on the splash as a fraction of the shorter side. Default `0.3`. */\n splashIconScale?: number;\n}\n\n/** A single Apple splash target (CSS px + device pixel ratio). */\nexport interface AppleSplashSpec {\n /** CSS width (device-width in the media query). */\n width: number;\n /** CSS height (device-height in the media query). */\n height: number;\n /** Device pixel ratio. */\n ratio: number;\n}\n\n/** Common iPhone/iPad portrait splash sizes (CSS px @ ratio). */\nconst DEFAULT_SPLASH: AppleSplashSpec[] = [\n { width: 375, height: 667, ratio: 2 }, // iPhone SE / 8\n { width: 375, height: 812, ratio: 3 }, // iPhone X / 11 Pro\n { width: 390, height: 844, ratio: 3 }, // iPhone 12 / 13 / 14\n { width: 393, height: 852, ratio: 3 }, // iPhone 14 Pro / 15\n { width: 414, height: 896, ratio: 2 }, // iPhone XR / 11\n { width: 414, height: 896, ratio: 3 }, // iPhone XS Max / 11 Pro Max\n { width: 428, height: 926, ratio: 3 }, // iPhone 13/14 Pro Max\n { width: 430, height: 932, ratio: 3 }, // iPhone 15 Pro Max\n { width: 768, height: 1024, ratio: 2 }, // iPad\n { width: 834, height: 1194, ratio: 2 }, // iPad Pro 11\"\n { width: 1024, height: 1366, ratio: 2 }, // iPad Pro 12.9\"\n];\n\nfunction splashFileName(spec: AppleSplashSpec): string {\n return `splash/apple-splash-${spec.width * spec.ratio}x${spec.height * spec.ratio}.png`;\n}\n\nfunction splashMedia(spec: AppleSplashSpec): string {\n return (\n `(device-width: ${spec.width}px) and (device-height: ${spec.height}px) ` +\n `and (-webkit-device-pixel-ratio: ${spec.ratio}) and (orientation: portrait)`\n );\n}\n\ninterface Rgb {\n r: number;\n g: number;\n b: number;\n}\n\nfunction hexToRgb(hex: string): Rgb {\n const value = hex.replace(\"#\", \"\");\n const full =\n value.length === 3\n ? value\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : value;\n return {\n r: parseInt(full.slice(0, 2), 16),\n g: parseInt(full.slice(2, 4), 16),\n b: parseInt(full.slice(4, 6), 16),\n };\n}\n\n/**\n * Build plugin that rasterizes a single source image into a full PWA icon set\n * (regular + maskable + apple-touch-icon), the dependency-free counterpart to\n * `@vite-pwa/assets-generator`. Rendering uses **`sharp`**, imported lazily and\n * treated as optional: if it isn't installed the plugin logs a warning and skips\n * generation (your build still succeeds; the icons just aren't produced).\n *\n * Point your `manifest.webmanifest` icon entries at the emitted files\n * (`/icons/icon-192.png`, `/icons/icon-512.png`, `/icons/maskable-512.png`) and\n * the apple touch icon at `/apple-touch-icon.png`.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestPwaIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * plugins: [tempestPwaIcons({ source: \"public/icon.svg\" })],\n * });\n */\nexport function tempestPwaIcons(options: TempestPwaIconsOptions = {}): TempestVitePlugin {\n const {\n source = \"public/icon.svg\",\n sizes = [192, 512],\n maskableSizes = [512],\n appleTouchIcon = 180,\n outDir = \"icons\",\n background = \"#ffffff\",\n maskablePadding = 0.1,\n appleSplash = false,\n splashBackground,\n splashIconScale = 0.3,\n } = options;\n\n const splashSpecs: AppleSplashSpec[] = appleSplash\n ? Array.isArray(appleSplash)\n ? appleSplash\n : DEFAULT_SPLASH\n : [];\n\n let root = process.cwd();\n\n const plugin: Plugin = {\n name: \"tempest-pwa-icons\",\n apply: \"build\",\n configResolved(config) {\n root = config.root ?? process.cwd();\n },\n transformIndexHtml() {\n if (!splashSpecs.length) return;\n return splashSpecs.map((spec) => ({\n tag: \"link\",\n attrs: {\n rel: \"apple-touch-startup-image\",\n media: splashMedia(spec),\n href: `/${splashFileName(spec)}`,\n },\n injectTo: \"head\" as const,\n }));\n },\n async generateBundle() {\n let sharp: SharpFactory;\n try {\n // Non-literal specifier so TS doesn't require `sharp`'s types\n // (it is an optional, lazily-loaded dependency).\n const specifier = \"sharp\";\n const mod = (await import(specifier)) as { default?: SharpFactory } & SharpFactory;\n sharp = (mod.default ?? mod) as SharpFactory;\n } catch {\n this.warn(\n \"tempestPwaIcons: `sharp` is not installed — skipping icon generation. \" +\n \"Run `npm i -D sharp` to enable it.\",\n );\n return;\n }\n\n const input = await readFile(resolve(root, source));\n const bg = hexToRgb(background);\n const emit = (fileName: string, data: Buffer): void => {\n this.emitFile({ type: \"asset\", fileName, source: data });\n };\n\n // Regular \"any\" icons — transparent background, full bleed.\n for (const size of sizes) {\n const png = await sharp(input, { density: Math.max(size, 512) })\n .resize(size, size, {\n fit: \"contain\",\n background: { r: 0, g: 0, b: 0, alpha: 0 },\n })\n .png()\n .toBuffer();\n emit(`${outDir}/icon-${size}.png`, png);\n }\n\n // Maskable icons — content shrunk into the safe zone over a solid bg.\n for (const size of maskableSizes) {\n const content = Math.round(size * (1 - maskablePadding * 2));\n const png = await sharp(input, { density: Math.max(size, 512) })\n .resize(content, content, {\n fit: \"contain\",\n background: { r: 0, g: 0, b: 0, alpha: 0 },\n })\n .extend({\n top: Math.round((size - content) / 2),\n bottom: Math.round((size - content) / 2),\n left: Math.round((size - content) / 2),\n right: Math.round((size - content) / 2),\n background: { ...bg, alpha: 1 },\n })\n .resize(size, size)\n .png()\n .toBuffer();\n emit(`${outDir}/maskable-${size}.png`, png);\n }\n\n // Apple touch icon — opaque, no alpha.\n if (appleTouchIcon) {\n const png = await sharp(input, { density: Math.max(appleTouchIcon, 512) })\n .resize(appleTouchIcon, appleTouchIcon, {\n fit: \"contain\",\n background: { ...bg, alpha: 1 },\n })\n .flatten({ background: bg })\n .png()\n .toBuffer();\n emit(\"apple-touch-icon.png\", png);\n }\n\n // Apple splash screens — icon centered on a solid background.\n if (splashSpecs.length) {\n const splashBg = hexToRgb(splashBackground ?? background);\n for (const spec of splashSpecs) {\n const w = spec.width * spec.ratio;\n const h = spec.height * spec.ratio;\n const iconPx = Math.round(Math.min(w, h) * splashIconScale);\n const icon = await sharp(input, { density: Math.max(iconPx, 512) })\n .resize(iconPx, iconPx, {\n fit: \"contain\",\n background: { r: 0, g: 0, b: 0, alpha: 0 },\n })\n .png()\n .toBuffer();\n const png = await sharp({\n create: {\n width: w,\n height: h,\n channels: 4,\n background: { ...splashBg, alpha: 1 },\n },\n })\n .composite([{ input: icon, gravity: \"center\" }])\n .png()\n .toBuffer();\n emit(splashFileName(spec), png);\n }\n }\n },\n };\n\n return plugin as TempestVitePlugin;\n}\n\n/** Options for the sharp `create` (blank canvas) form. */\ninterface SharpCreate {\n create: {\n width: number;\n height: number;\n channels: number;\n background: { r: number; g: number; b: number; alpha: number };\n };\n}\n\n/** The sharp factory function (minimal typing — sharp is an optional dep). */\ntype SharpFactory = (input: Buffer | SharpCreate, opts?: { density?: number }) => SharpInstance;\n\n/** Minimal subset of the sharp chainable API this plugin uses. */\ninterface SharpInstance {\n resize(\n width: number,\n height: number,\n opts?: { fit?: string; background?: { r: number; g: number; b: number; alpha: number } },\n ): SharpInstance;\n extend(opts: {\n top: number;\n bottom: number;\n left: number;\n right: number;\n background: { r: number; g: number; b: number; alpha: number };\n }): SharpInstance;\n flatten(opts: { background: Rgb }): SharpInstance;\n composite(items: { input: Buffer; gravity?: string }[]): SharpInstance;\n png(): SharpInstance;\n toBuffer(): Promise<Buffer>;\n}\n","import { resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** Options for {@link tempestPwaDevSw}. */\nexport interface TempestPwaDevSwOptions {\n /** Service-worker entry, relative to the project root. Default `src/sw.ts`. */\n swSrc?: string;\n /** URL the worker is served at (must match `registerServiceWorker`). Default `/sw.js`. */\n swUrl?: string;\n /** Dev URL of the precache manifest. Default `/precache-manifest.json`. */\n manifestUrl?: string;\n /** Serve the worker in dev. Default `true`; set `false` to opt out. */\n enabled?: boolean;\n}\n\n/**\n * Dev-server plugin that makes the service worker available under `npm run dev`.\n *\n * The production worker is bundled at build time (`vite.sw.config.ts`), so in\n * dev there is no `/sw.js` to register. This plugin compiles `swSrc` on the fly\n * with esbuild and serves it as a classic worker, plus an empty\n * `precache-manifest.json` (there are no hashed build assets to precache in\n * dev — push and runtime caching still work). It closes the \"SW in dev\" gap\n * that otherwise only `vite-plugin-pwa`'s `devOptions` covered.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestPwaDevSw } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestPwaDevSw()] });\n */\nexport function tempestPwaDevSw(options: TempestPwaDevSwOptions = {}): TempestVitePlugin {\n const {\n swSrc = \"src/sw.ts\",\n swUrl = \"/sw.js\",\n manifestUrl = \"/precache-manifest.json\",\n enabled = true,\n } = options;\n\n let root = process.cwd();\n\n const plugin: Plugin = {\n name: \"tempest-pwa-dev-sw\",\n apply: \"serve\",\n configResolved(config) {\n root = config.root ?? process.cwd();\n },\n configureServer(server) {\n if (!enabled) return;\n server.middlewares.use(async (req, res, next) => {\n const url = (req.url ?? \"\").split(\"?\")[0];\n\n if (url === swUrl) {\n try {\n const esbuild = await import(\"esbuild\");\n const result = await esbuild.build({\n entryPoints: [resolve(root, swSrc)],\n bundle: true,\n format: \"iife\",\n platform: \"browser\",\n target: \"es2020\",\n write: false,\n absWorkingDir: root,\n logLevel: \"silent\",\n });\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Service-Worker-Allowed\", \"/\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.outputFiles[0].text);\n } catch (error) {\n res.statusCode = 500;\n res.end(`// SW dev build failed:\\n// ${String(error)}`);\n }\n return;\n }\n\n if (url === manifestUrl) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(JSON.stringify({ version: \"dev\", urls: [] }));\n return;\n }\n\n next();\n });\n },\n };\n\n return plugin as TempestVitePlugin;\n}\n"],"names":["normalizeProxy","proxy","out","path","value","createViteConfig","options","srcDir","port","host","open","alias","plugins","overrides","overridesConfig","base","react","resolve","merged","defineConfig","joinBase","file","hash","input","h","i","tempestPwaManifest","fileName","additionalUrls","exclude","includeHtml","appShell","config","_outputOptions","bundle","urls","list","version","DEFAULT_SPLASH","splashFileName","spec","splashMedia","hexToRgb","hex","full","c","tempestPwaIcons","source","sizes","maskableSizes","appleTouchIcon","outDir","background","maskablePadding","appleSplash","splashBackground","splashIconScale","splashSpecs","root","sharp","mod","readFile","bg","emit","data","size","png","content","splashBg","w","iconPx","icon","tempestPwaDevSw","swSrc","swUrl","manifestUrl","enabled","server","req","res","next","url","result","error"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tempest-react-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "SDK público da Tempest com componentes, hooks e integrações para projetos React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/icon.svg" />
6
- <link rel="apple-touch-icon" href="/icon.svg" />
6
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
8
8
  <meta name="theme-color" content="#0b1a36" />
9
9
  <meta name="description" content="App React fiado com tempest-react-sdk." />
@@ -2,5 +2,8 @@
2
2
  "scripts": {
3
3
  "build": "tsc --noEmit && vite build && npm run build:sw",
4
4
  "build:sw": "vite build --config vite.sw.config.ts"
5
+ },
6
+ "devDependencies": {
7
+ "sharp": "^0.34.0"
5
8
  }
6
9
  }
@@ -14,21 +14,21 @@
14
14
  "categories": ["productivity", "utilities"],
15
15
  "icons": [
16
16
  {
17
- "src": "/icon.svg",
17
+ "src": "/icons/icon-192.png",
18
18
  "sizes": "192x192",
19
- "type": "image/svg+xml",
19
+ "type": "image/png",
20
20
  "purpose": "any"
21
21
  },
22
22
  {
23
- "src": "/icon.svg",
23
+ "src": "/icons/icon-512.png",
24
24
  "sizes": "512x512",
25
- "type": "image/svg+xml",
25
+ "type": "image/png",
26
26
  "purpose": "any"
27
27
  },
28
28
  {
29
- "src": "/icon-maskable.svg",
29
+ "src": "/icons/maskable-512.png",
30
30
  "sizes": "512x512",
31
- "type": "image/svg+xml",
31
+ "type": "image/png",
32
32
  "purpose": "maskable"
33
33
  }
34
34
  ]
@@ -10,27 +10,20 @@ createRoot(document.getElementById("root")!).render(
10
10
  </StrictMode>,
11
11
  );
12
12
 
13
- // The service worker is bundled to `/sw.js` at build time, so it only exists in
14
- // production. In dev we proactively unregister any leftover worker to avoid a
15
- // stale cache shadowing your `npm run dev` output.
16
- if (import.meta.env.DEV && "serviceWorker" in navigator) {
17
- void navigator.serviceWorker
18
- .getRegistrations()
19
- .then((regs) => Promise.all(regs.map((r) => r.unregister())))
20
- .catch((err) => console.warn("[sw] dev cleanup failed", err));
21
- }
22
-
23
- if (import.meta.env.PROD) {
24
- void registerServiceWorker({
25
- url: "/sw.js",
26
- onUpdate: (waiting) => {
27
- // A new worker is ready. Prompt your users however you like; here we
28
- // just activate it and reload so the next visit is up to date.
29
- if (confirm("Nova versão disponível. Atualizar agora?")) {
30
- skipWaiting(waiting);
31
- window.location.reload();
32
- }
33
- },
34
- onError: (err) => console.warn("[sw] registration failed", err),
35
- });
36
- }
13
+ // Register the service worker in both dev and prod. In production it's the
14
+ // bundled `/sw.js` (built by `vite.sw.config.ts`); in dev the `tempestPwaDevSw`
15
+ // plugin (in `vite.config.ts`) compiles and serves it on the fly, so push and
16
+ // runtime caching work under `npm run dev` too. Drop the `tempestPwaDevSw`
17
+ // plugin if you'd rather keep dev SW-free.
18
+ void registerServiceWorker({
19
+ url: "/sw.js",
20
+ onUpdate: (waiting) => {
21
+ // A new worker is ready. Prompt your users however you like; here we
22
+ // just activate it and reload so the next visit is up to date.
23
+ if (confirm("Nova versão disponível. Atualizar agora?")) {
24
+ skipWaiting(waiting);
25
+ window.location.reload();
26
+ }
27
+ },
28
+ onError: (err) => console.warn("[sw] registration failed", err),
29
+ });
@@ -1,7 +1,10 @@
1
1
  /// <reference lib="webworker" />
2
2
  import {
3
+ installBackgroundSync,
3
4
  installNotificationClickHandler,
5
+ installPrecache,
4
6
  installPushHandler,
7
+ installRuntimeCache,
5
8
  installSkipWaitingListener,
6
9
  } from "tempest-react-sdk/sw";
7
10
 
@@ -9,14 +12,17 @@ import {
9
12
  * Service worker. Bundled to `dist/sw.js` by `vite.sw.config.ts` (see the
10
13
  * `build:sw` script) and registered from `src/main.tsx`.
11
14
  *
12
- * The three SDK helpers wire the boilerplate:
13
- * - `installPushHandler` parses the `push` payload (JSON, plain-text fallback)
14
- * and shows a notification.
15
- * - `installNotificationClickHandler` focuses an open tab on click, or opens a
16
- * new one at the payload's `url`.
17
- * - `installSkipWaitingListener` activates a waiting worker when the page posts
18
- * `{ type: "SKIP_WAITING" }` (paired with `registerServiceWorker`'s
19
- * `onUpdate` in `main.tsx`).
15
+ * Layers, in order:
16
+ * 1. Push + notifications + skip-waiting (the SDK push helpers).
17
+ * 2. Background sync — failed API mutations are queued and replayed online.
18
+ * 3. Runtime caching: API GETs (network-first) + media (cache-first with
19
+ * Range support, so audio/video seeking works offline).
20
+ * 4. Precache of the app shell so the app launches offline. Reads the
21
+ * `precache-manifest.json` emitted by `tempestPwaManifest()` in
22
+ * `vite.config.ts`.
23
+ *
24
+ * `installRuntimeCache` is registered BEFORE `installPrecache` so its specific
25
+ * routes win over the precache catch-all.
20
26
  */
21
27
  declare const self: ServiceWorkerGlobalScope;
22
28
 
@@ -29,7 +35,36 @@ installPushHandler({
29
35
  installNotificationClickHandler();
30
36
  installSkipWaitingListener();
31
37
 
32
- // Take control of open pages as soon as this worker activates.
38
+ // Queue failed POST/PUT/PATCH/DELETE to /api and replay them when back online.
39
+ installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
40
+
41
+ installRuntimeCache([
42
+ {
43
+ match: (url) => url.pathname.startsWith("/api/"),
44
+ strategy: "network-first",
45
+ cacheName: "api",
46
+ networkTimeoutSeconds: 5,
47
+ maxEntries: 50,
48
+ maxAgeSeconds: 60 * 5,
49
+ },
50
+ {
51
+ // Audio/video: cache-first + Range support (seek/scrub works offline).
52
+ match: (url) => /\.(mp3|mp4|webm|ogg|wav)$/.test(url.pathname),
53
+ strategy: "cache-first",
54
+ cacheName: "media",
55
+ maxEntries: 20,
56
+ rangeRequests: true,
57
+ },
58
+ ]);
59
+
60
+ installPrecache({
61
+ navigateFallback: "/index.html",
62
+ // Don't serve the app shell for API navigations.
63
+ navigateFallbackDenylist: [/^\/api\//],
64
+ });
65
+
66
+ // Take control of open pages as soon as this worker activates. `installPrecache`
67
+ // also calls `clients.claim()`; this is harmless if you drop precaching.
33
68
  self.addEventListener("activate", (event) => {
34
69
  event.waitUntil(self.clients.claim());
35
70
  });
@@ -0,0 +1,21 @@
1
+ import {
2
+ createViteConfig,
3
+ tempestPwaDevSw,
4
+ tempestPwaIcons,
5
+ tempestPwaManifest,
6
+ } from "tempest-react-sdk/vite";
7
+
8
+ // `createViteConfig` wires `@vitejs/plugin-react`, the `@` → `src` alias and
9
+ // dev-server defaults. The three PWA plugins (order matters):
10
+ // - tempestPwaIcons — rasterizes public/icon.svg into the PNG icon set (sharp).
11
+ // - tempestPwaManifest — emits dist/precache-manifest.json (it sees the icons
12
+ // above because they're emitted first), for offline app-shell precaching.
13
+ // - tempestPwaDevSw — serves /sw.js (and an empty manifest) under `npm run dev`.
14
+ export default createViteConfig({
15
+ // proxy: { "/api": "http://127.0.0.1:8000" },
16
+ plugins: [
17
+ tempestPwaIcons({ source: "public/icon.svg", appleSplash: true }),
18
+ tempestPwaManifest({ additionalUrls: ["/manifest.webmanifest", "/icon.svg"] }),
19
+ tempestPwaDevSw(),
20
+ ],
21
+ });
@@ -1,4 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Tempest">
2
- <rect width="512" height="512" fill="#0b1a36" />
3
- <path d="M176 200h160l-30 50h-54v140l-46 24V250h-54z" fill="#ffffff" />
4
- </svg>