tempest-react-sdk 0.25.0 → 0.26.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/README.md +14 -1
- package/bin/lib/alias/collect.mjs +94 -0
- package/bin/lib/alias/collect.test.mjs +95 -0
- package/bin/lib/alias/discover.mjs +77 -0
- package/bin/lib/alias/discover.test.mjs +189 -0
- package/bin/lib/alias/index.mjs +88 -0
- package/bin/lib/alias/index.test.mjs +138 -0
- package/bin/lib/alias/rewrite-css.mjs +76 -0
- package/bin/lib/alias/rewrite-css.test.mjs +85 -0
- package/bin/lib/alias/rewrite.mjs +141 -0
- package/bin/lib/alias/rewrite.test.mjs +148 -0
- package/bin/lib/alias/specifier.mjs +49 -0
- package/bin/lib/alias/specifier.test.mjs +85 -0
- package/bin/lib/alias/tsconfig.mjs +116 -0
- package/bin/lib/alias/typescript.mjs +23 -0
- package/bin/lib/icons/generate.mjs +156 -0
- package/bin/lib/icons/generate.test.mjs +125 -0
- package/bin/tempest.mjs +158 -13
- package/dist/audio/use-audio.cjs +1 -1
- package/dist/audio/use-audio.cjs.map +1 -1
- package/dist/audio/use-audio.js +12 -12
- package/dist/audio/use-audio.js.map +1 -1
- package/dist/charts/palette.cjs +1 -1
- package/dist/charts/palette.cjs.map +1 -1
- package/dist/charts/palette.js +2 -2
- package/dist/charts/palette.js.map +1 -1
- package/dist/charts.d.ts +5 -3
- package/dist/components/ClickOutside/ClickOutside.cjs +1 -1
- package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
- package/dist/components/ClickOutside/ClickOutside.js +3 -1
- package/dist/components/ClickOutside/ClickOutside.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +21 -23
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/Kanban/Kanban.cjs +2 -0
- package/dist/components/Kanban/Kanban.cjs.map +1 -0
- package/dist/components/Kanban/Kanban.js +102 -0
- package/dist/components/Kanban/Kanban.js.map +1 -0
- package/dist/components/Kanban/Kanban.module.cjs +2 -0
- package/dist/components/Kanban/Kanban.module.cjs.map +1 -0
- package/dist/components/Kanban/Kanban.module.js +19 -0
- package/dist/components/Kanban/Kanban.module.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.cjs +2 -0
- package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.js +83 -0
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.module.cjs +2 -0
- package/dist/components/NotificationCenter/NotificationCenter.module.cjs.map +1 -0
- package/dist/components/NotificationCenter/NotificationCenter.module.js +21 -0
- package/dist/components/NotificationCenter/NotificationCenter.module.js.map +1 -0
- package/dist/components/NotificationCenter/use-notification-inbox.cjs +2 -0
- package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -0
- package/dist/components/NotificationCenter/use-notification-inbox.js +63 -0
- package/dist/components/NotificationCenter/use-notification-inbox.js.map +1 -0
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.js +17 -17
- package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +26 -20
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.cjs +2 -0
- package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -0
- package/dist/components/VirtualTable/VirtualTable.js +116 -0
- package/dist/components/VirtualTable/VirtualTable.js.map +1 -0
- package/dist/components/VirtualTable/VirtualTable.module.cjs +2 -0
- package/dist/components/VirtualTable/VirtualTable.module.cjs.map +1 -0
- package/dist/components/VirtualTable/VirtualTable.module.js +20 -0
- package/dist/components/VirtualTable/VirtualTable.module.js.map +1 -0
- package/dist/components/Wizard/Wizard.cjs +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js +53 -53
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/geo/use-position-tracker.cjs +1 -1
- package/dist/geo/use-position-tracker.cjs.map +1 -1
- package/dist/geo/use-position-tracker.js +3 -1
- package/dist/geo/use-position-tracker.js.map +1 -1
- package/dist/hooks/use-async.cjs +1 -1
- package/dist/hooks/use-async.cjs.map +1 -1
- package/dist/hooks/use-async.js +3 -1
- package/dist/hooks/use-async.js.map +1 -1
- package/dist/hooks/use-click-outside.cjs +1 -1
- package/dist/hooks/use-click-outside.cjs.map +1 -1
- package/dist/hooks/use-click-outside.js +3 -1
- package/dist/hooks/use-click-outside.js.map +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.js.map +1 -1
- package/dist/hooks/use-event-listener.cjs +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-event-listener.js +3 -1
- package/dist/hooks/use-event-listener.js.map +1 -1
- package/dist/hooks/use-interval.cjs +1 -1
- package/dist/hooks/use-interval.cjs.map +1 -1
- package/dist/hooks/use-interval.js +3 -1
- package/dist/hooks/use-interval.js.map +1 -1
- package/dist/hooks/use-is-first-render.cjs.map +1 -1
- package/dist/hooks/use-is-first-render.js.map +1 -1
- package/dist/hooks/use-long-press-handlers.cjs +1 -1
- package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
- package/dist/hooks/use-long-press-handlers.js +25 -23
- package/dist/hooks/use-long-press-handlers.js.map +1 -1
- package/dist/hooks/use-long-press.cjs +1 -1
- package/dist/hooks/use-long-press.cjs.map +1 -1
- package/dist/hooks/use-long-press.js +3 -1
- package/dist/hooks/use-long-press.js.map +1 -1
- package/dist/hooks/use-online.cjs +1 -1
- package/dist/hooks/use-online.cjs.map +1 -1
- package/dist/hooks/use-online.js +6 -4
- package/dist/hooks/use-online.js.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-previous.js.map +1 -1
- package/dist/hooks/use-service-worker-update.cjs +1 -1
- package/dist/hooks/use-service-worker-update.cjs.map +1 -1
- package/dist/hooks/use-service-worker-update.js +3 -1
- package/dist/hooks/use-service-worker-update.js.map +1 -1
- package/dist/hooks/use-sortable.cjs +2 -0
- package/dist/hooks/use-sortable.cjs.map +1 -0
- package/dist/hooks/use-sortable.js +169 -0
- package/dist/hooks/use-sortable.js.map +1 -0
- package/dist/hooks/use-stable-callback.cjs.map +1 -1
- package/dist/hooks/use-stable-callback.js.map +1 -1
- package/dist/hooks/use-timeout.cjs +1 -1
- package/dist/hooks/use-timeout.cjs.map +1 -1
- package/dist/hooks/use-timeout.js +3 -1
- package/dist/hooks/use-timeout.js.map +1 -1
- package/dist/icons/Icon.cjs +2 -0
- package/dist/icons/Icon.cjs.map +1 -0
- package/dist/icons/Icon.js +24 -0
- package/dist/icons/Icon.js.map +1 -0
- package/dist/icons/IconProvider.cjs +2 -0
- package/dist/icons/IconProvider.cjs.map +1 -0
- package/dist/icons/IconProvider.js +24 -0
- package/dist/icons/IconProvider.js.map +1 -0
- package/dist/icons/generated/aliases.cjs +2 -0
- package/dist/icons/generated/aliases.cjs.map +1 -0
- package/dist/icons/generated/aliases.js +255 -0
- package/dist/icons/generated/aliases.js.map +1 -0
- package/dist/icons/generated/icon-names.cjs +2 -0
- package/dist/icons/generated/icon-names.cjs.map +1 -0
- package/dist/icons/generated/icon-names.js +6 -0
- package/dist/icons/generated/icon-names.js.map +1 -0
- package/dist/icons/generated/loaders.cjs +2 -0
- package/dist/icons/generated/loaders.cjs.map +1 -0
- package/dist/icons/generated/loaders.js +32 -0
- package/dist/icons/generated/loaders.js.map +1 -0
- package/dist/icons/generated/shard-a.cjs +2 -0
- package/dist/icons/generated/shard-a.cjs.map +1 -0
- package/dist/icons/generated/shard-a.js +114 -0
- package/dist/icons/generated/shard-a.js.map +1 -0
- package/dist/icons/generated/shard-b.cjs +2 -0
- package/dist/icons/generated/shard-b.cjs.map +1 -0
- package/dist/icons/generated/shard-b.js +156 -0
- package/dist/icons/generated/shard-b.js.map +1 -0
- package/dist/icons/generated/shard-c.cjs +2 -0
- package/dist/icons/generated/shard-c.cjs.map +1 -0
- package/dist/icons/generated/shard-c.js +269 -0
- package/dist/icons/generated/shard-c.js.map +1 -0
- package/dist/icons/generated/shard-d.cjs +2 -0
- package/dist/icons/generated/shard-d.cjs.map +1 -0
- package/dist/icons/generated/shard-d.js +62 -0
- package/dist/icons/generated/shard-d.js.map +1 -0
- package/dist/icons/generated/shard-e.cjs +2 -0
- package/dist/icons/generated/shard-e.cjs.map +1 -0
- package/dist/icons/generated/shard-e.js +32 -0
- package/dist/icons/generated/shard-e.js.map +1 -0
- package/dist/icons/generated/shard-f.cjs +2 -0
- package/dist/icons/generated/shard-f.cjs.map +1 -0
- package/dist/icons/generated/shard-f.js +139 -0
- package/dist/icons/generated/shard-f.js.map +1 -0
- package/dist/icons/generated/shard-g.cjs +2 -0
- package/dist/icons/generated/shard-g.cjs.map +1 -0
- package/dist/icons/generated/shard-g.js +61 -0
- package/dist/icons/generated/shard-g.js.map +1 -0
- package/dist/icons/generated/shard-h.cjs +2 -0
- package/dist/icons/generated/shard-h.cjs.map +1 -0
- package/dist/icons/generated/shard-h.js +63 -0
- package/dist/icons/generated/shard-h.js.map +1 -0
- package/dist/icons/generated/shard-i.cjs +2 -0
- package/dist/icons/generated/shard-i.cjs.map +1 -0
- package/dist/icons/generated/shard-i.js +30 -0
- package/dist/icons/generated/shard-i.js.map +1 -0
- package/dist/icons/generated/shard-j.cjs +2 -0
- package/dist/icons/generated/shard-j.cjs.map +1 -0
- package/dist/icons/generated/shard-j.js +10 -0
- package/dist/icons/generated/shard-j.js.map +1 -0
- package/dist/icons/generated/shard-k.cjs +2 -0
- package/dist/icons/generated/shard-k.cjs.map +1 -0
- package/dist/icons/generated/shard-k.js +16 -0
- package/dist/icons/generated/shard-k.js.map +1 -0
- package/dist/icons/generated/shard-l.cjs +2 -0
- package/dist/icons/generated/shard-l.cjs.map +1 -0
- package/dist/icons/generated/shard-l.js +88 -0
- package/dist/icons/generated/shard-l.js.map +1 -0
- package/dist/icons/generated/shard-m.cjs +2 -0
- package/dist/icons/generated/shard-m.cjs.map +1 -0
- package/dist/icons/generated/shard-m.js +140 -0
- package/dist/icons/generated/shard-m.js.map +1 -0
- package/dist/icons/generated/shard-n.cjs +2 -0
- package/dist/icons/generated/shard-n.cjs.map +1 -0
- package/dist/icons/generated/shard-n.js +24 -0
- package/dist/icons/generated/shard-n.js.map +1 -0
- package/dist/icons/generated/shard-o.cjs +2 -0
- package/dist/icons/generated/shard-o.cjs.map +1 -0
- package/dist/icons/generated/shard-o.js +17 -0
- package/dist/icons/generated/shard-o.js.map +1 -0
- package/dist/icons/generated/shard-p.cjs +2 -0
- package/dist/icons/generated/shard-p.cjs.map +1 -0
- package/dist/icons/generated/shard-p.js +115 -0
- package/dist/icons/generated/shard-p.js.map +1 -0
- package/dist/icons/generated/shard-q.cjs +2 -0
- package/dist/icons/generated/shard-q.cjs.map +1 -0
- package/dist/icons/generated/shard-q.js +10 -0
- package/dist/icons/generated/shard-q.js.map +1 -0
- package/dist/icons/generated/shard-r.cjs +2 -0
- package/dist/icons/generated/shard-r.cjs.map +1 -0
- package/dist/icons/generated/shard-r.js +77 -0
- package/dist/icons/generated/shard-r.js.map +1 -0
- package/dist/icons/generated/shard-s.cjs +2 -0
- package/dist/icons/generated/shard-s.cjs.map +1 -0
- package/dist/icons/generated/shard-s.js +255 -0
- package/dist/icons/generated/shard-s.js.map +1 -0
- package/dist/icons/generated/shard-t.cjs +2 -0
- package/dist/icons/generated/shard-t.cjs.map +1 -0
- package/dist/icons/generated/shard-t.js +109 -0
- package/dist/icons/generated/shard-t.js.map +1 -0
- package/dist/icons/generated/shard-u.cjs +2 -0
- package/dist/icons/generated/shard-u.cjs.map +1 -0
- package/dist/icons/generated/shard-u.js +49 -0
- package/dist/icons/generated/shard-u.js.map +1 -0
- package/dist/icons/generated/shard-v.cjs +2 -0
- package/dist/icons/generated/shard-v.cjs.map +1 -0
- package/dist/icons/generated/shard-v.js +30 -0
- package/dist/icons/generated/shard-v.js.map +1 -0
- package/dist/icons/generated/shard-w.cjs +2 -0
- package/dist/icons/generated/shard-w.cjs.map +1 -0
- package/dist/icons/generated/shard-w.js +48 -0
- package/dist/icons/generated/shard-w.js.map +1 -0
- package/dist/icons/generated/shard-x.cjs +2 -0
- package/dist/icons/generated/shard-x.cjs.map +1 -0
- package/dist/icons/generated/shard-x.js +10 -0
- package/dist/icons/generated/shard-x.js.map +1 -0
- package/dist/icons/generated/shard-z.cjs +2 -0
- package/dist/icons/generated/shard-z.cjs.map +1 -0
- package/dist/icons/generated/shard-z.js +25 -0
- package/dist/icons/generated/shard-z.js.map +1 -0
- package/dist/icons/icon-context.cjs +2 -0
- package/dist/icons/icon-context.cjs.map +1 -0
- package/dist/icons/icon-context.js +13 -0
- package/dist/icons/icon-context.js.map +1 -0
- package/dist/icons/is-icon-name.cjs +2 -0
- package/dist/icons/is-icon-name.cjs.map +1 -0
- package/dist/icons/is-icon-name.js +10 -0
- package/dist/icons/is-icon-name.js.map +1 -0
- package/dist/icons/shard-cache.cjs +2 -0
- package/dist/icons/shard-cache.cjs.map +1 -0
- package/dist/icons/shard-cache.js +41 -0
- package/dist/icons/shard-cache.js.map +1 -0
- package/dist/icons/use-icon.cjs +2 -0
- package/dist/icons/use-icon.cjs.map +1 -0
- package/dist/icons/use-icon.js +20 -0
- package/dist/icons/use-icon.js.map +1 -0
- package/dist/icons-virtual.d.ts +22 -0
- package/dist/icons.cjs +1 -0
- package/dist/icons.d.ts +220 -0
- package/dist/icons.js +9 -0
- package/dist/offline/use-offline-sync.cjs +1 -1
- package/dist/offline/use-offline-sync.cjs.map +1 -1
- package/dist/offline/use-offline-sync.js +3 -1
- package/dist/offline/use-offline-sync.js.map +1 -1
- package/dist/push/use-push-subscription.cjs +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/push/use-push-subscription.js +3 -1
- package/dist/push/use-push-subscription.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +3 -1
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +585 -3
- package/dist/tempest-react-sdk.js +189 -183
- package/dist/theme/ThemeProvider.cjs +1 -1
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/ThemeProvider.js +6 -2
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/color.cjs +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js +77 -46
- package/dist/theme/color.js.map +1 -1
- package/dist/theme/create-theme.cjs +1 -1
- package/dist/theme/create-theme.cjs.map +1 -1
- package/dist/theme/create-theme.js +9 -3
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/utils/compare-values.cjs +2 -0
- package/dist/utils/compare-values.cjs.map +1 -0
- package/dist/utils/compare-values.js +8 -0
- package/dist/utils/compare-values.js.map +1 -0
- package/dist/vision/use-camera-stream.cjs +1 -1
- package/dist/vision/use-camera-stream.cjs.map +1 -1
- package/dist/vision/use-camera-stream.js +3 -1
- package/dist/vision/use-camera-stream.js.map +1 -1
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +30 -22
- package/dist/vite/create-vite-config.js.map +1 -1
- package/dist/vite/tempest-icons.cjs +9 -0
- package/dist/vite/tempest-icons.cjs.map +1 -0
- package/dist/vite/tempest-icons.js +99 -0
- package/dist/vite/tempest-icons.js.map +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +79 -0
- package/dist/vite.js +6 -5
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +3 -1
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +12 -3
- package/template/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ Via `package.json`:
|
|
|
149
149
|
```json
|
|
150
150
|
{
|
|
151
151
|
"dependencies": {
|
|
152
|
-
"tempest-react-sdk": "^0.
|
|
152
|
+
"tempest-react-sdk": "^0.26.0"
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
```
|
|
@@ -198,6 +198,17 @@ This injects the design tokens (`--tempest-primary`, `--tempest-radius-md`, ...)
|
|
|
198
198
|
|
|
199
199
|
The styles ship hashed under the `tempest_` namespace — they do **not** collide with Tailwind, Stitches, Linaria, or app-level CSS Modules.
|
|
200
200
|
|
|
201
|
+
**Rebranding.** The `--tempest-*` tokens are the only theming API, and `createTheme` writes them for you — `primary` alone yields the ten-step scale for **both** color schemes (dark inverts the ramp), plus the status families, the radius scale and the focus ring:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { applyTheme, createTheme, themePresets } from "tempest-react-sdk";
|
|
205
|
+
|
|
206
|
+
applyTheme(createTheme({ primary: "#7c3aed", radius: "lg" }));
|
|
207
|
+
applyTheme(createTheme(themePresets.violet)); // or start from a preset
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The ramp is derived in OKLCH (HSL lightness is not perceptual, which is what makes generated palettes look broken for some hues) and **anchored at step `500`**, so the color you pass is the color your buttons get. `--tempest-primary-foreground` and the text-on-soft step are picked by **measured** contrast, not convention — hardcoding white breaks a yellow brand. See [Theme › createTheme](https://mauriciobenjamin700.github.io/tempest-react-sdk/theme/).
|
|
211
|
+
|
|
201
212
|
**Optional layout layer.** CSS Modules cover the inside of each component; the layer _around_ them (page shell, two-column form, action row, card, a region that scrolls sideways instead of the page) is a second, opt-in stylesheet:
|
|
202
213
|
|
|
203
214
|
```ts
|
|
@@ -232,6 +243,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
232
243
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
233
244
|
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`), `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
234
245
|
| `charts` _(subpath `tempest-react-sdk/charts`, opt. peer `recharts`)_ | `AreaChart`, `BarChart`, `LineChart`, `PieChart`, `RadarChart`, `DEFAULT_CHART_COLORS` — themed recharts wrappers (recharts externalized; install it only if you use charts) |
|
|
246
|
+
| `icons` _(subpath `tempest-react-sdk/icons`)_ | `Icon`, `IconProvider`, `createIconRegistry`, `useIcon`, `preloadIcons`, `iconStatus`, `peekIcon`, `loadIcon`, `resolveIconAlias`, `isIconName`, `iconNames`, `iconAliases`, type `IconName` — render any of lucide's **1997 icons by kebab-case slug**. A literal slug becomes a static import via the `tempestIcons()` Vite plugin (zero extra requests); a runtime slug loads **one chunk per initial letter** (25 max) instead of the ~2000 chunk boundaries `lucide-react`'s own `DynamicIcon` forces. Unknown slug renders `fallback` and never throws; lucide's 248 deprecated aliases keep resolving |
|
|
235
247
|
| `audio` | `createAudioPlayer`, `playAudio`, `stopAudio`, `useAudio` |
|
|
236
248
|
| `offline` _(peer: `dexie`)_ | `createOfflineStore`, `createOfflineSync` (outbox + delta-pull + watermark engine, now with `subscribe`/`getState`/`dispose` + cross-tab `crossTab`), `useOfflineSync`/`useSyncStatus` (reactive hooks), `lastWriteWins`/`higherVersionWins` (conflict resolvers), types: `OfflineStore`, `OfflineStoreConfig`, `ListOptions`, `OfflineSync`, `OfflineSyncConfig`, `OutboxEntry`, `PullPage`, `SyncPhase`, `SyncState`, `SyncRunSummary`, `SyncTrigger`, `SyncStatus`, `SyncTone`, `WatermarkStore` |
|
|
237
249
|
| `error-boundary` | `ErrorBoundary`, `useErrorHandler`, types: `ErrorBoundaryProps`, `ErrorBoundaryRenderProps` |
|
|
@@ -260,6 +272,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
260
272
|
| `ws` | `createWebSocket`, `useWebSocket` |
|
|
261
273
|
| `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
|
|
262
274
|
| `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
|
|
275
|
+
| `icons` _(subpath `tempest-react-sdk/icons`)_ | `Icon`, `IconProvider`, `createIconRegistry`, `useIcon`, `preloadIcons`, `iconStatus`, `peekIcon`, `loadIcon`, `resolveIconAlias`, `isIconName`, `iconNames`, `iconAliases`, type `IconName` — render any of lucide's **1997 icons by kebab-case slug**. A literal slug becomes a static import via the `tempestIcons()` Vite plugin (zero extra requests); a runtime slug loads **one chunk per initial letter** (25 max) instead of the ~2000 chunk boundaries `lucide-react`'s own `DynamicIcon` forces. Unknown slug renders `fallback` and never throws; lucide's 248 deprecated aliases keep resolving |
|
|
263
276
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
264
277
|
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`), `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
265
278
|
| `editor` _(subpath `tempest-react-sdk/editor`, opt. peers `@tiptap/react`+`@tiptap/starter-kit`)_ | `RichTextEditor` — themed tiptap v3 rich-text editor with toolbar (tiptap externalized; install it only if you use the editor) |
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Walk the paths the user asked for and keep the files the codemod can act on.
|
|
2
|
+
import { readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { extname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { isInsideBase } from "./specifier.mjs";
|
|
6
|
+
|
|
7
|
+
const SOURCE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
8
|
+
const STYLE_EXTS = new Set([".css"]);
|
|
9
|
+
const SKIP_DIRS = new Set([
|
|
10
|
+
"node_modules",
|
|
11
|
+
"dist",
|
|
12
|
+
"build",
|
|
13
|
+
"coverage",
|
|
14
|
+
".git",
|
|
15
|
+
".vite",
|
|
16
|
+
".next",
|
|
17
|
+
".turbo",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Classify a file by extension.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} path
|
|
24
|
+
* @returns {"source" | "style" | null}
|
|
25
|
+
*/
|
|
26
|
+
function kindOf(path) {
|
|
27
|
+
const ext = extname(path);
|
|
28
|
+
if (SOURCE_EXTS.has(ext)) return "source";
|
|
29
|
+
if (STYLE_EXTS.has(ext)) return "style";
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collect the files to rewrite, honoring the positional paths given to the CLI.
|
|
35
|
+
*
|
|
36
|
+
* Everything outside the alias base is dropped here as well as in the rewriters:
|
|
37
|
+
* the walk never leaves the base, so `tempest fix .` on a project root descends
|
|
38
|
+
* straight into `src/` and skips `vite.config.ts`, `e2e/` and `scripts/` without
|
|
39
|
+
* needing to open them. A target that is an ancestor of the base is narrowed to
|
|
40
|
+
* the base; a target that is merely *outside* it yields nothing, so
|
|
41
|
+
* `tempest fix e2e` rewrites nothing instead of quietly rewriting `src/`.
|
|
42
|
+
*
|
|
43
|
+
* Results are sorted so the report — and any diff a reviewer reads — is stable
|
|
44
|
+
* across runs and platforms.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} params
|
|
47
|
+
* @param {string} params.root - Project root; relative targets resolve against it.
|
|
48
|
+
* @param {string[]} params.targets - Positional paths from the command line.
|
|
49
|
+
* @param {string} params.baseDir - Absolute alias base directory.
|
|
50
|
+
* @returns {Array<{ path: string, kind: "source" | "style" }>}
|
|
51
|
+
*/
|
|
52
|
+
export function collectFiles({ root, targets, baseDir }) {
|
|
53
|
+
const out = new Map();
|
|
54
|
+
|
|
55
|
+
const addFile = (path) => {
|
|
56
|
+
if (out.has(path) || !isInsideBase(path, baseDir)) return;
|
|
57
|
+
const kind = kindOf(path);
|
|
58
|
+
if (kind) out.set(path, { path, kind });
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const walk = (dir) => {
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
70
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
71
|
+
const full = join(dir, entry.name);
|
|
72
|
+
if (entry.isDirectory()) walk(full);
|
|
73
|
+
else if (entry.isFile()) addFile(full);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const target of targets) {
|
|
78
|
+
const full = resolve(root, target);
|
|
79
|
+
let stat;
|
|
80
|
+
try {
|
|
81
|
+
stat = statSync(full);
|
|
82
|
+
} catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (stat.isDirectory()) {
|
|
86
|
+
if (full === baseDir || isInsideBase(full, baseDir)) walk(full);
|
|
87
|
+
else if (isInsideBase(baseDir, full)) walk(baseDir);
|
|
88
|
+
} else if (stat.isFile()) {
|
|
89
|
+
addFile(full);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return [...out.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
94
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { collectFiles } from "./collect.mjs";
|
|
7
|
+
|
|
8
|
+
let root;
|
|
9
|
+
let baseDir;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
root = mkdtempSync(join(tmpdir(), "tempest-collect-"));
|
|
13
|
+
baseDir = join(root, "src");
|
|
14
|
+
for (const rel of [
|
|
15
|
+
"src/main.tsx",
|
|
16
|
+
"src/lib/api.ts",
|
|
17
|
+
"src/lib/api.test.ts",
|
|
18
|
+
"src/styles/tokens.css",
|
|
19
|
+
"src/legacy/old.js",
|
|
20
|
+
"src/legacy/old.mjs",
|
|
21
|
+
"src/assets/logo.svg",
|
|
22
|
+
"src/README.md",
|
|
23
|
+
"src/node_modules/dep/index.ts",
|
|
24
|
+
"src/.cache/x.ts",
|
|
25
|
+
"vite.config.ts",
|
|
26
|
+
"e2e/spec.ts",
|
|
27
|
+
"dist/bundle.js",
|
|
28
|
+
]) {
|
|
29
|
+
const path = join(root, rel);
|
|
30
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
31
|
+
writeFileSync(path, "");
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
rmSync(root, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** Collect and return repo-relative paths with a POSIX separator. */
|
|
40
|
+
function collect(targets) {
|
|
41
|
+
return collectFiles({ root, targets, baseDir }).map((f) => ({
|
|
42
|
+
path: relative(root, f.path).split("\\").join("/"),
|
|
43
|
+
kind: f.kind,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("collectFiles — extensions", () => {
|
|
48
|
+
it("keeps source and style files, drops everything else", () => {
|
|
49
|
+
expect(collect(["."])).toEqual([
|
|
50
|
+
{ path: "src/legacy/old.js", kind: "source" },
|
|
51
|
+
{ path: "src/legacy/old.mjs", kind: "source" },
|
|
52
|
+
{ path: "src/lib/api.test.ts", kind: "source" },
|
|
53
|
+
{ path: "src/lib/api.ts", kind: "source" },
|
|
54
|
+
{ path: "src/main.tsx", kind: "source" },
|
|
55
|
+
{ path: "src/styles/tokens.css", kind: "style" },
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
it("drops an asset that carries no specifier of its own", () => {
|
|
59
|
+
expect(collect(["src/assets/logo.svg"])).toEqual([]);
|
|
60
|
+
expect(collect(["src/README.md"])).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("collectFiles — scope", () => {
|
|
65
|
+
it("narrows a project-root target to the alias base", () => {
|
|
66
|
+
expect(collect(["."]).every((f) => f.path.startsWith("src/"))).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("restricts to a positional subdirectory", () => {
|
|
69
|
+
expect(collect(["src/lib"]).map((f) => f.path)).toEqual([
|
|
70
|
+
"src/lib/api.test.ts",
|
|
71
|
+
"src/lib/api.ts",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
it("accepts a single file target", () => {
|
|
75
|
+
expect(collect(["src/main.tsx"]).map((f) => f.path)).toEqual(["src/main.tsx"]);
|
|
76
|
+
});
|
|
77
|
+
it("yields nothing for a target outside the alias base", () => {
|
|
78
|
+
expect(collect(["e2e"])).toEqual([]);
|
|
79
|
+
expect(collect(["vite.config.ts"])).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
it("skips node_modules, dist and dot directories inside the base", () => {
|
|
82
|
+
const paths = collect(["."]).map((f) => f.path);
|
|
83
|
+
expect(paths.some((p) => p.includes("node_modules"))).toBe(false);
|
|
84
|
+
expect(paths.some((p) => p.includes(".cache"))).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it("ignores a target that does not exist", () => {
|
|
87
|
+
expect(collect(["src/nope"])).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
it("deduplicates overlapping targets", () => {
|
|
90
|
+
expect(collect(["src/lib", "src/lib/api.ts"]).map((f) => f.path)).toEqual([
|
|
91
|
+
"src/lib/api.test.ts",
|
|
92
|
+
"src/lib/api.ts",
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Work out which path alias a project uses (`@/*` → `src/*`) and where its base
|
|
2
|
+
// directory is, so the codemod rewrites to the project's own convention.
|
|
3
|
+
import { statSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { readTsconfig } from "./tsconfig.mjs";
|
|
7
|
+
|
|
8
|
+
/** True when `path` exists and is a directory. */
|
|
9
|
+
function isDir(path) {
|
|
10
|
+
try {
|
|
11
|
+
return statSync(path).isDirectory();
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pick the first usable `<prefix>/*` → `<dir>/*` mapping from tsconfig `paths`.
|
|
19
|
+
*
|
|
20
|
+
* Entries are skipped rather than guessed at when they are ambiguous: more than
|
|
21
|
+
* one target (the compiler tries each in order, so there is no single directory
|
|
22
|
+
* to rewrite to), a non-wildcard key, or a target whose directory is missing.
|
|
23
|
+
* Insertion order decides between several usable entries, so the result is
|
|
24
|
+
* stable across runs.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} paths - Raw `compilerOptions.paths`.
|
|
27
|
+
* @param {string} baseDir - Directory the targets resolve against.
|
|
28
|
+
* @returns {{ prefix: string, baseDir: string } | null}
|
|
29
|
+
*/
|
|
30
|
+
function fromPaths(paths, baseDir) {
|
|
31
|
+
for (const [key, targets] of Object.entries(paths)) {
|
|
32
|
+
if (!key.endsWith("/*") || !Array.isArray(targets) || targets.length !== 1) continue;
|
|
33
|
+
const target = targets[0];
|
|
34
|
+
if (typeof target !== "string" || !target.endsWith("/*")) continue;
|
|
35
|
+
const dir = resolve(baseDir, target.slice(0, -2));
|
|
36
|
+
if (!isDir(dir)) continue;
|
|
37
|
+
return { prefix: key.slice(0, -2), baseDir: dir };
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Discover the alias prefix and its base directory for a project.
|
|
44
|
+
*
|
|
45
|
+
* The tsconfig `paths` entry is the *only* accepted source, deliberately: it is
|
|
46
|
+
* what the type-checker honors, so an alias found there is one `tsc --noEmit`
|
|
47
|
+
* will accept afterwards. Inferring `@` → `src` from the directory layout was
|
|
48
|
+
* tried and rejected — a project can perfectly well have a `src/` and no alias
|
|
49
|
+
* at all (the SDK's own `examples/gallery` is one), and rewriting its imports
|
|
50
|
+
* would produce specifiers that resolve nowhere.
|
|
51
|
+
*
|
|
52
|
+
* `extends` chains and JSONC comments are handled by `readTsconfig`, so a project
|
|
53
|
+
* that keeps `paths` in a shared base config is still recognized.
|
|
54
|
+
*
|
|
55
|
+
* Returning `null` is a normal outcome, not an error: the caller reports it and
|
|
56
|
+
* leaves the source untouched rather than inventing a convention.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} params
|
|
59
|
+
* @param {string} params.root - Project root.
|
|
60
|
+
* @param {object | null} params.ts - The `typescript` module, or `null`.
|
|
61
|
+
* @returns {{ prefix: string, baseDir: string, source: "tsconfig" } | null}
|
|
62
|
+
*/
|
|
63
|
+
export function discoverAlias({ root, ts }) {
|
|
64
|
+
const tsconfig = readTsconfig({ root, ts });
|
|
65
|
+
const paths = tsconfig?.compilerOptions?.paths;
|
|
66
|
+
if (!paths || typeof paths !== "object") return null;
|
|
67
|
+
|
|
68
|
+
const baseUrl = tsconfig.compilerOptions.baseUrl;
|
|
69
|
+
const pathsBase =
|
|
70
|
+
typeof baseUrl === "string"
|
|
71
|
+
? isAbsolute(baseUrl)
|
|
72
|
+
? baseUrl
|
|
73
|
+
: resolve(tsconfig.pathsBaseDir, baseUrl)
|
|
74
|
+
: tsconfig.pathsBaseDir;
|
|
75
|
+
const found = fromPaths(paths, pathsBase);
|
|
76
|
+
return found ? { ...found, source: "tsconfig" } : null;
|
|
77
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { discoverAlias } from "./discover.mjs";
|
|
7
|
+
import { readTsconfig } from "./tsconfig.mjs";
|
|
8
|
+
import { loadTypeScript } from "./typescript.mjs";
|
|
9
|
+
|
|
10
|
+
const ts = loadTypeScript(process.cwd());
|
|
11
|
+
|
|
12
|
+
let root;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
root = mkdtempSync(join(tmpdir(), "tempest-alias-"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
rmSync(root, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** Write a file under the fixture root, creating parent directories. */
|
|
23
|
+
function write(rel, contents) {
|
|
24
|
+
const path = join(root, rel);
|
|
25
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
26
|
+
writeFileSync(path, contents);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Create a directory under the fixture root. */
|
|
30
|
+
function dir(rel) {
|
|
31
|
+
mkdirSync(join(root, rel), { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("readTsconfig", () => {
|
|
35
|
+
it("returns null when there is no tsconfig", () => {
|
|
36
|
+
expect(readTsconfig({ root, ts })).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
it("reads plain JSON", () => {
|
|
39
|
+
write("tsconfig.json", JSON.stringify({ compilerOptions: { strict: true } }));
|
|
40
|
+
expect(readTsconfig({ root, ts }).compilerOptions.strict).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it("reads JSONC — comments and trailing commas", () => {
|
|
43
|
+
write(
|
|
44
|
+
"tsconfig.json",
|
|
45
|
+
`{
|
|
46
|
+
// the alias the SDK expects
|
|
47
|
+
"compilerOptions": {
|
|
48
|
+
"moduleResolution": "bundler", /* keeps subpath types resolvable */
|
|
49
|
+
"paths": { "@/*": ["./src/*"] },
|
|
50
|
+
},
|
|
51
|
+
}`,
|
|
52
|
+
);
|
|
53
|
+
const cfg = readTsconfig({ root, ts });
|
|
54
|
+
expect(cfg.compilerOptions.moduleResolution).toBe("bundler");
|
|
55
|
+
expect(cfg.compilerOptions.paths).toEqual({ "@/*": ["./src/*"] });
|
|
56
|
+
});
|
|
57
|
+
it("keeps option values raw instead of normalizing them to enums", () => {
|
|
58
|
+
write(
|
|
59
|
+
"tsconfig.json",
|
|
60
|
+
JSON.stringify({ compilerOptions: { moduleResolution: "bundler", jsx: "react-jsx" } }),
|
|
61
|
+
);
|
|
62
|
+
const { compilerOptions } = readTsconfig({ root, ts });
|
|
63
|
+
expect(compilerOptions.moduleResolution).toBe("bundler");
|
|
64
|
+
expect(compilerOptions.jsx).toBe("react-jsx");
|
|
65
|
+
});
|
|
66
|
+
it("merges the extends chain with the extending file winning", () => {
|
|
67
|
+
write(
|
|
68
|
+
"tsconfig.base.json",
|
|
69
|
+
JSON.stringify({ compilerOptions: { strict: false, skipLibCheck: true } }),
|
|
70
|
+
);
|
|
71
|
+
write(
|
|
72
|
+
"tsconfig.json",
|
|
73
|
+
JSON.stringify({ extends: "./tsconfig.base.json", compilerOptions: { strict: true } }),
|
|
74
|
+
);
|
|
75
|
+
const { compilerOptions } = readTsconfig({ root, ts });
|
|
76
|
+
expect(compilerOptions.strict).toBe(true);
|
|
77
|
+
expect(compilerOptions.skipLibCheck).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it("resolves extends without the .json extension", () => {
|
|
80
|
+
write("base.json", JSON.stringify({ compilerOptions: { skipLibCheck: true } }));
|
|
81
|
+
write("tsconfig.json", JSON.stringify({ extends: "./base" }));
|
|
82
|
+
expect(readTsconfig({ root, ts }).compilerOptions.skipLibCheck).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it("falls back to JSON.parse when typescript is unavailable", () => {
|
|
85
|
+
write("tsconfig.json", JSON.stringify({ compilerOptions: { strict: true } }));
|
|
86
|
+
expect(readTsconfig({ root, ts: null }).compilerOptions.strict).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it("returns null for unparseable JSON without typescript", () => {
|
|
89
|
+
write("tsconfig.json", "{ not json");
|
|
90
|
+
expect(readTsconfig({ root, ts: null })).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("discoverAlias — from tsconfig paths", () => {
|
|
95
|
+
it("finds the @/* → src/* mapping", () => {
|
|
96
|
+
dir("src");
|
|
97
|
+
write(
|
|
98
|
+
"tsconfig.json",
|
|
99
|
+
JSON.stringify({ compilerOptions: { paths: { "@/*": ["./src/*"] } } }),
|
|
100
|
+
);
|
|
101
|
+
expect(discoverAlias({ root, ts })).toEqual({
|
|
102
|
+
prefix: "@",
|
|
103
|
+
baseDir: join(root, "src"),
|
|
104
|
+
source: "tsconfig",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it("honours a non-@ prefix", () => {
|
|
108
|
+
dir("src");
|
|
109
|
+
write(
|
|
110
|
+
"tsconfig.json",
|
|
111
|
+
JSON.stringify({ compilerOptions: { paths: { "~/*": ["./src/*"] } } }),
|
|
112
|
+
);
|
|
113
|
+
expect(discoverAlias({ root, ts }).prefix).toBe("~");
|
|
114
|
+
});
|
|
115
|
+
it("honours an app/ base", () => {
|
|
116
|
+
dir("app");
|
|
117
|
+
write(
|
|
118
|
+
"tsconfig.json",
|
|
119
|
+
JSON.stringify({ compilerOptions: { paths: { "@/*": ["./app/*"] } } }),
|
|
120
|
+
);
|
|
121
|
+
expect(discoverAlias({ root, ts }).baseDir).toBe(join(root, "app"));
|
|
122
|
+
});
|
|
123
|
+
it("resolves paths against baseUrl when one is set", () => {
|
|
124
|
+
dir("client/src");
|
|
125
|
+
write(
|
|
126
|
+
"tsconfig.json",
|
|
127
|
+
JSON.stringify({
|
|
128
|
+
compilerOptions: { baseUrl: "./client", paths: { "@/*": ["./src/*"] } },
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
expect(discoverAlias({ root, ts }).baseDir).toBe(join(root, "client", "src"));
|
|
132
|
+
});
|
|
133
|
+
it("finds paths inherited through extends, resolved against the declaring file", () => {
|
|
134
|
+
dir("src");
|
|
135
|
+
write(
|
|
136
|
+
"tsconfig.base.json",
|
|
137
|
+
JSON.stringify({ compilerOptions: { paths: { "@/*": ["./src/*"] } } }),
|
|
138
|
+
);
|
|
139
|
+
write("tsconfig.json", JSON.stringify({ extends: "./tsconfig.base.json" }));
|
|
140
|
+
expect(discoverAlias({ root, ts })).toEqual({
|
|
141
|
+
prefix: "@",
|
|
142
|
+
baseDir: join(root, "src"),
|
|
143
|
+
source: "tsconfig",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
it("skips an ambiguous entry with several targets", () => {
|
|
147
|
+
dir("src");
|
|
148
|
+
write(
|
|
149
|
+
"tsconfig.json",
|
|
150
|
+
JSON.stringify({ compilerOptions: { paths: { "@/*": ["./src/*", "./generated/*"] } } }),
|
|
151
|
+
);
|
|
152
|
+
expect(discoverAlias({ root, ts })).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
it("skips a non-wildcard entry", () => {
|
|
155
|
+
dir("src");
|
|
156
|
+
write(
|
|
157
|
+
"tsconfig.json",
|
|
158
|
+
JSON.stringify({ compilerOptions: { paths: { "@shared": ["./src/shared.ts"] } } }),
|
|
159
|
+
);
|
|
160
|
+
expect(discoverAlias({ root, ts })).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
it("skips an entry whose target directory does not exist", () => {
|
|
163
|
+
dir("src");
|
|
164
|
+
write(
|
|
165
|
+
"tsconfig.json",
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
compilerOptions: { paths: { "#/*": ["./nope/*"], "@/*": ["./src/*"] } },
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
expect(discoverAlias({ root, ts })).toMatchObject({ prefix: "@", source: "tsconfig" });
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("discoverAlias — refuses to guess", () => {
|
|
175
|
+
it("returns null when a src/ exists but nothing declares an alias", () => {
|
|
176
|
+
dir("src");
|
|
177
|
+
expect(discoverAlias({ root, ts })).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
it("returns null when there is no tsconfig at all", () => {
|
|
180
|
+
dir("src");
|
|
181
|
+
dir("app");
|
|
182
|
+
expect(discoverAlias({ root, ts })).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
it("returns null when the tsconfig declares no paths", () => {
|
|
185
|
+
dir("src");
|
|
186
|
+
write("tsconfig.json", JSON.stringify({ compilerOptions: { strict: true } }));
|
|
187
|
+
expect(discoverAlias({ root, ts })).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// The `tempest fix` alias pass: discover the convention, rewrite the files,
|
|
2
|
+
// report what changed.
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { relative } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { collectFiles } from "./collect.mjs";
|
|
7
|
+
import { discoverAlias } from "./discover.mjs";
|
|
8
|
+
import { rewriteCss } from "./rewrite-css.mjs";
|
|
9
|
+
import { rewriteSource } from "./rewrite.mjs";
|
|
10
|
+
import { loadTypeScript } from "./typescript.mjs";
|
|
11
|
+
|
|
12
|
+
export { collectFiles } from "./collect.mjs";
|
|
13
|
+
export { discoverAlias } from "./discover.mjs";
|
|
14
|
+
export { rewriteCss } from "./rewrite-css.mjs";
|
|
15
|
+
export { rewriteSource } from "./rewrite.mjs";
|
|
16
|
+
export { isInsideBase, toAlias } from "./specifier.mjs";
|
|
17
|
+
export { readTsconfig } from "./tsconfig.mjs";
|
|
18
|
+
export { loadTypeScript } from "./typescript.mjs";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert relative imports that climb out of their directory into alias imports.
|
|
22
|
+
*
|
|
23
|
+
* Reports instead of throwing when the project is not set up for it — no
|
|
24
|
+
* TypeScript installed, no alias discoverable. Both are "nothing to do", not
|
|
25
|
+
* failures, so the surrounding `fix` keeps its exit code and still runs ESLint
|
|
26
|
+
* and Prettier.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} params
|
|
29
|
+
* @param {string} params.root - Project root.
|
|
30
|
+
* @param {string[]} params.targets - Positional paths from the command line.
|
|
31
|
+
* @param {boolean} [params.dryRun] - Report the changes without writing them.
|
|
32
|
+
* @returns {{
|
|
33
|
+
* status: "ok" | "no-typescript" | "no-alias" | "error",
|
|
34
|
+
* prefix?: string,
|
|
35
|
+
* baseDir?: string,
|
|
36
|
+
* files: Array<{ path: string, changes: Array<{ from: string, to: string, line: number }> }>,
|
|
37
|
+
* total: number,
|
|
38
|
+
* errors: Array<{ path: string, message: string }>,
|
|
39
|
+
* }}
|
|
40
|
+
*/
|
|
41
|
+
export function aliasImports({ root, targets, dryRun = false }) {
|
|
42
|
+
const empty = { files: [], total: 0, errors: [] };
|
|
43
|
+
|
|
44
|
+
const ts = loadTypeScript(root);
|
|
45
|
+
if (!ts) return { status: "no-typescript", ...empty };
|
|
46
|
+
|
|
47
|
+
const alias = discoverAlias({ root, ts });
|
|
48
|
+
if (!alias) return { status: "no-alias", ...empty };
|
|
49
|
+
|
|
50
|
+
const { prefix, baseDir } = alias;
|
|
51
|
+
const files = [];
|
|
52
|
+
const errors = [];
|
|
53
|
+
let total = 0;
|
|
54
|
+
|
|
55
|
+
for (const { path, kind } of collectFiles({ root, targets, baseDir })) {
|
|
56
|
+
let text;
|
|
57
|
+
try {
|
|
58
|
+
text = readFileSync(path, "utf8");
|
|
59
|
+
} catch (err) {
|
|
60
|
+
errors.push({ path: relative(root, path), message: String(err?.message ?? err) });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const result =
|
|
64
|
+
kind === "style"
|
|
65
|
+
? rewriteCss({ text, filePath: path, prefix, baseDir })
|
|
66
|
+
: rewriteSource({ text, filePath: path, prefix, baseDir, ts });
|
|
67
|
+
if (!result.changes.length) continue;
|
|
68
|
+
if (!dryRun) {
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(path, result.text);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
errors.push({ path: relative(root, path), message: String(err?.message ?? err) });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
files.push({ path: relative(root, path), changes: result.changes });
|
|
77
|
+
total += result.changes.length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
status: errors.length ? "error" : "ok",
|
|
82
|
+
prefix,
|
|
83
|
+
baseDir,
|
|
84
|
+
files,
|
|
85
|
+
total,
|
|
86
|
+
errors,
|
|
87
|
+
};
|
|
88
|
+
}
|