@rozenite/vite-plugin 2.1.0 → 2.3.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.
Files changed (39) hide show
  1. package/README.md +8 -19
  2. package/dist/client-plugin.d.ts.map +1 -1
  3. package/dist/dev-host/assets/index-CB6OyiMD.css +1 -0
  4. package/dist/dev-host/assets/index-D1SCI-r0.js +13 -0
  5. package/dist/dev-host/index.html +2 -2
  6. package/dist/dev-host/manifest.json +2 -2
  7. package/dist/index.cjs +31 -432
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +31 -432
  11. package/dist/load-config.d.ts +20 -0
  12. package/dist/load-config.d.ts.map +1 -1
  13. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  14. package/package.json +5 -5
  15. package/src/client-plugin.ts +16 -1
  16. package/src/dev-host/components/DispatchForm.tsx +7 -14
  17. package/src/dev-host/components/FlowList.tsx +18 -4
  18. package/src/dev-host/components/MessageDetailsPane.tsx +9 -11
  19. package/src/dev-host/components/MessageLogPane.tsx +5 -6
  20. package/src/dev-host/styles.css +2 -2
  21. package/src/index.ts +15 -89
  22. package/src/load-config.ts +44 -0
  23. package/dist/bundle-dts.d.ts +0 -5
  24. package/dist/bundle-dts.d.ts.map +0 -1
  25. package/dist/dev-host/assets/index-BgAxGQdD.js +0 -13
  26. package/dist/dev-host/assets/index-ClIYIppU.css +0 -1
  27. package/dist/react-native-plugin.d.ts +0 -3
  28. package/dist/react-native-plugin.d.ts.map +0 -1
  29. package/dist/require-plugin.d.ts +0 -3
  30. package/dist/require-plugin.d.ts.map +0 -1
  31. package/dist/sdk-plugin.d.ts +0 -3
  32. package/dist/sdk-plugin.d.ts.map +0 -1
  33. package/dist/server-plugin.d.ts +0 -3
  34. package/dist/server-plugin.d.ts.map +0 -1
  35. package/src/bundle-dts.ts +0 -117
  36. package/src/react-native-plugin.ts +0 -51
  37. package/src/require-plugin.ts +0 -209
  38. package/src/sdk-plugin.ts +0 -47
  39. package/src/server-plugin.ts +0 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/vite-plugin",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Vite plugin for developing React Native DevTools plugins with hot reload and development tools",
5
5
  "keywords": [
6
6
  "devtools",
@@ -40,21 +40,21 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@microsoft/api-extractor": "^7.50.1",
44
43
  "@tailwindcss/vite": "4.2.2",
45
44
  "@vitejs/plugin-react": "^4.3.4",
46
45
  "ejs": "^3.1.9",
47
46
  "lucide-react": "^0.263.1",
48
- "vite-plugin-dts": "~4.5.0",
49
47
  "vite-plugin-react-native-web": "^2.1.1",
50
- "@rozenite/ui": "2.1.0"
48
+ "@rozenite/tools": "2.3.0",
49
+ "@rozenite/ui": "2.3.0"
51
50
  },
52
51
  "devDependencies": {
53
52
  "@types/ejs": "^3.1.5",
54
53
  "@types/react": "~19.2.3",
55
54
  "@types/react-dom": "~19.2.3",
56
55
  "react": "19.2.3",
57
- "react-dom": "19.2.3"
56
+ "react-dom": "19.2.3",
57
+ "vite-plugin-dts": "~4.5.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "react": "*",
@@ -5,7 +5,8 @@ import process from 'node:process';
5
5
  import ejs from 'ejs';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { normalizePath } from 'vite';
8
- import { loadConfig, RozeniteConfig } from './load-config.js';
8
+ import type { RozeniteIntegration } from '@rozenite/tools';
9
+ import { loadConfig, resolveIntegrations, RozeniteConfig } from './load-config.js';
9
10
  import { getPackageJSON } from './package-json.js';
10
11
  import { DEV_HOST_CONFIG_GLOBAL_KEY, DEV_HOST_STATE_ELEMENT_ID } from './dev-host/constants.js';
11
12
  import {
@@ -88,6 +89,14 @@ export const rozeniteClientPlugin = (): Plugin => {
88
89
  }));
89
90
  };
90
91
 
92
+ // Derived at emission time from the loaded config, like `getManifestPanels`
93
+ // above, rather than cached into plugin state by the `config` hook: a
94
+ // cached value would silently emit a manifest claiming the plugin supports
95
+ // nothing if that hook were ever skipped or reordered.
96
+ const getManifestIntegrations = (): RozeniteIntegration[] => {
97
+ return resolveIntegrations(getRozeniteConfig());
98
+ };
99
+
91
100
  const getDevHostPanels = (): DevHostPanelEntry[] => {
92
101
  return getPanels().map((panel) => ({
93
102
  label: panel.label,
@@ -166,6 +175,10 @@ export const rozeniteClientPlugin = (): Plugin => {
166
175
  }
167
176
 
168
177
  rozeniteConfig = await loadConfig(getRozeniteConfigPath(projectRoot));
178
+ // Called for its validation only: a typo'd integration id must fail
179
+ // the build here, at config-load time, rather than quietly shipping a
180
+ // manifest that makes the plugin unloadable everywhere.
181
+ resolveIntegrations(rozeniteConfig);
169
182
  const panels = getPanels();
170
183
 
171
184
  config.server ??= {};
@@ -305,6 +318,7 @@ export const rozeniteClientPlugin = (): Plugin => {
305
318
  version: packageJSON.version,
306
319
  description: packageJSON.description,
307
320
  panels: getManifestPanels(),
321
+ integrations: getManifestIntegrations(),
308
322
  },
309
323
  null,
310
324
  2,
@@ -362,6 +376,7 @@ export const rozeniteClientPlugin = (): Plugin => {
362
376
  version: packageJSON.version,
363
377
  description: packageJSON.description,
364
378
  panels: getManifestPanels(),
379
+ integrations: getManifestIntegrations(),
365
380
  }),
366
381
  });
367
382
  },
@@ -1,4 +1,4 @@
1
- import { Button, Input, Select, Tabs, Textarea } from '@rozenite/ui';
1
+ import { IconButton, Input, Select, Tabs, Textarea } from '@rozenite/ui';
2
2
  import { RotateCcw, Send } from 'lucide-react';
3
3
  import { useState, type FormEvent } from 'react';
4
4
  import type { DevHostFlowEntry, DevHostFlowRunState, DevHostPresetEntry } from '../types.js';
@@ -108,24 +108,17 @@ export const DispatchForm = ({
108
108
  </label>
109
109
 
110
110
  <div className="dev-host-actions">
111
- <Button
111
+ <IconButton
112
+ tone="neutral"
112
113
  variant="outline"
113
- size="icon"
114
114
  onClick={onReset}
115
- aria-label="Reset dispatcher"
116
- title="Reset dispatcher"
115
+ label="Reset dispatcher"
117
116
  >
118
117
  <RotateCcw />
119
- </Button>
120
- <Button
121
- type="submit"
122
- size="icon"
123
- disabled={!canDispatch}
124
- aria-label="Dispatch message"
125
- title="Dispatch message"
126
- >
118
+ </IconButton>
119
+ <IconButton type="submit" disabled={!canDispatch} label="Dispatch message">
127
120
  <Send />
128
- </Button>
121
+ </IconButton>
129
122
  </div>
130
123
  </form>
131
124
  </Tabs.Panel>
@@ -52,7 +52,8 @@ export const FlowList = ({
52
52
  return (
53
53
  <Button
54
54
  key={flow.name}
55
- variant={isRunning ? 'secondary' : 'outline'}
55
+ tone="neutral"
56
+ variant={isRunning ? 'solid' : 'outline'}
56
57
  className="dev-host-flow-button"
57
58
  onClick={() => onRunFlow(flow)}
58
59
  aria-label={`Run ${flow.displayName}`}
@@ -60,7 +61,11 @@ export const FlowList = ({
60
61
  <span className="dev-host-flow-name">
61
62
  <Play />
62
63
  {flow.displayName}
63
- {flow.autoRun ? <Badge variant="outline">Auto</Badge> : null}
64
+ {flow.autoRun ? (
65
+ <Badge tone="neutral" variant="outline">
66
+ Auto
67
+ </Badge>
68
+ ) : null}
64
69
  </span>
65
70
  <span className="text-xs text-muted-foreground">
66
71
  {isRunning ? 'Running' : 'Run'}
@@ -80,10 +85,19 @@ export const FlowList = ({
80
85
  <div className="dev-host-flow-state-header">
81
86
  <span className="dev-host-flow-title">
82
87
  {flowRun.flowDisplayName}
83
- {flowRun.autoRun ? <Badge variant="outline">Auto</Badge> : null}
88
+ {flowRun.autoRun ? (
89
+ <Badge tone="neutral" variant="outline">
90
+ Auto
91
+ </Badge>
92
+ ) : null}
84
93
  </span>
85
94
  {flowRun.status === 'running' ? (
86
- <Button variant="ghost" size="compact" onClick={() => onStopFlow(flowRun.id)}>
95
+ <Button
96
+ tone="neutral"
97
+ variant="ghost"
98
+ size="sm"
99
+ onClick={() => onStopFlow(flowRun.id)}
100
+ >
87
101
  <Square />
88
102
  Stop
89
103
  </Button>
@@ -1,4 +1,4 @@
1
- import { Button, JsonInspector, ScrollArea } from '@rozenite/ui';
1
+ import { IconButton, JsonInspector, ScrollArea } from '@rozenite/ui';
2
2
  import { Send, X } from 'lucide-react';
3
3
  import type { MessageEntry } from '../types.js';
4
4
  import { formatMessageDate, formatPayloadForCommandInput } from '../utils.js';
@@ -19,24 +19,22 @@ export const MessageDetailsPane = ({
19
19
  <header className="dev-host-pane-header">
20
20
  <h2 className="dev-host-pane-title">Message Details</h2>
21
21
  <div className="dev-host-pane-actions">
22
- <Button
22
+ <IconButton
23
+ tone="neutral"
23
24
  variant="ghost"
24
- size="icon"
25
25
  onClick={() => onUseMessage(selectedMessage)}
26
- aria-label="Use message in dispatcher"
27
- title="Use message in dispatcher"
26
+ label="Use message in dispatcher"
28
27
  >
29
28
  <Send />
30
- </Button>
31
- <Button
29
+ </IconButton>
30
+ <IconButton
31
+ tone="neutral"
32
32
  variant="ghost"
33
- size="icon"
34
33
  onClick={onClose}
35
- aria-label="Close message details"
36
- title="Close message details"
34
+ label="Close message details"
37
35
  >
38
36
  <X />
39
- </Button>
37
+ </IconButton>
40
38
  </div>
41
39
  </header>
42
40
 
@@ -1,4 +1,4 @@
1
- import { Button, DataTable, ScrollArea } from '@rozenite/ui';
1
+ import { DataTable, IconButton, ScrollArea } from '@rozenite/ui';
2
2
  import { Trash2 } from 'lucide-react';
3
3
  import { useMemo } from 'react';
4
4
  import type { DataTableColumn } from '@rozenite/ui';
@@ -66,16 +66,15 @@ export const MessageLogPane = ({
66
66
  <section className="dev-host-pane">
67
67
  <header className="dev-host-pane-header">
68
68
  <h2 className="dev-host-pane-title">Message Log</h2>
69
- <Button
69
+ <IconButton
70
+ tone="neutral"
70
71
  variant="ghost"
71
- size="icon"
72
72
  onClick={onClearMessages}
73
73
  disabled={messages.length === 0}
74
- aria-label="Clear message log"
75
- title="Clear message log"
74
+ label="Clear message log"
76
75
  >
77
76
  <Trash2 />
78
- </Button>
77
+ </IconButton>
79
78
  </header>
80
79
 
81
80
  <ScrollArea className="dev-host-scroll" viewportClassName="dev-host-scroll-viewport">
@@ -219,7 +219,7 @@
219
219
  }
220
220
 
221
221
  .dev-host-flow-state[data-status='failed'] {
222
- border-color: var(--destructive);
222
+ border-color: var(--danger);
223
223
  }
224
224
 
225
225
  .dev-host-flow-state-header {
@@ -246,7 +246,7 @@
246
246
  }
247
247
 
248
248
  .dev-host-flow-state-error {
249
- color: var(--destructive);
249
+ color: var(--danger);
250
250
  }
251
251
 
252
252
  .dev-host-detail-section {
package/src/index.ts CHANGED
@@ -2,80 +2,16 @@ import type { PluginOption } from 'vite';
2
2
  import tailwindcss from '@tailwindcss/vite';
3
3
  import react from '@vitejs/plugin-react';
4
4
  import reactNativeWeb from 'vite-plugin-react-native-web';
5
- import path from 'node:path';
6
- import { rozeniteServerPlugin } from './server-plugin.js';
7
5
  import { rozeniteClientPlugin } from './client-plugin.js';
8
- import { rozeniteReactNativePlugin } from './react-native-plugin.js';
9
- import { rozeniteSdkPlugin } from './sdk-plugin.js';
10
- import maybeDtsPlugin from 'vite-plugin-dts';
11
- import requirePlugin from './require-plugin.js';
12
- import { bundleTargetDeclarations } from './bundle-dts.js';
13
6
 
14
- // vite-plugin-dts exports differently in CJS and ESM
15
- const dtsPlugin =
16
- 'default' in maybeDtsPlugin ? (maybeDtsPlugin.default as typeof maybeDtsPlugin) : maybeDtsPlugin;
17
-
18
- const getDtsPlugin = (target: 'react-native' | 'metro' | 'sdk'): PluginOption => {
19
- const projectRoot = process.cwd();
20
- const entryRoot =
21
- target === 'react-native' ? 'react-native.ts' : target === 'metro' ? 'metro.ts' : 'sdk.ts';
22
- const distRoot = path.join(projectRoot, 'dist');
23
- const targetRoot = path.join(distRoot, target);
24
- const publicEntryPath = path.join(projectRoot, 'dist', target, 'index.d.ts');
25
- const sdkBundleEntryPath = path.join(targetRoot, `${target}.d.ts`);
26
- const rawSdkEntryPath = path.join(distRoot, `${target}.d.ts`);
27
-
28
- return dtsPlugin({
29
- entryRoot,
30
- include: [entryRoot, 'src/**/*.ts', 'src/**/*.tsx', 'src/**/*.d.ts'],
31
- outDir: `dist/${target}`,
32
- strictOutput: false,
33
- insertTypesEntry: false,
34
- // Preserve package specifiers in published declarations instead of
35
- // rewriting workspace imports to source file paths.
36
- pathsToAliases: false,
37
- tsconfigPath: path.join(projectRoot, 'tsconfig.json'),
38
- beforeWriteFile: (filePath, content) => {
39
- if (!filePath.endsWith('.d.ts')) {
40
- return false;
41
- }
42
-
43
- if (
44
- filePath === publicEntryPath ||
45
- (target !== 'sdk' && filePath.endsWith(`/${target}.d.ts`))
46
- ) {
47
- return {
48
- filePath: publicEntryPath,
49
- content,
50
- };
51
- }
52
-
53
- if (target === 'sdk' && filePath === rawSdkEntryPath) {
54
- return {
55
- // vite-plugin-dts emits the SDK root declaration at dist/sdk.d.ts.
56
- // Move it under dist/sdk/ before API Extractor rolls it up into the
57
- // published dist/sdk/index.d.ts.
58
- filePath: sdkBundleEntryPath,
59
- content,
60
- };
61
- }
62
-
63
- const relativeToDist = path.relative(distRoot, filePath);
64
-
65
- if (!relativeToDist.startsWith('src/')) {
66
- return false;
67
- }
68
-
69
- return {
70
- filePath: path.join(targetRoot, relativeToDist),
71
- content,
72
- };
73
- },
74
- afterBuild: async () => {
75
- await bundleTargetDeclarations(projectRoot, target);
76
- },
77
- });
78
- };
7
+ // Re-exported so plugin authors can type `rozenite.config.ts` with
8
+ // `satisfies RozeniteConfig`, which is what turns a typo'd integration id
9
+ // into an editor error instead of a build failure later. `RozeniteConfig`
10
+ // is the config file's own shape; `RozeniteIntegration` comes from
11
+ // `@rozenite/tools`, re-exported here so authors need not depend on that
12
+ // package directly for one type.
13
+ export type { RozeniteConfig, PanelEntry } from './load-config.js';
14
+ export type { RozeniteIntegration } from '@rozenite/tools';
79
15
 
80
16
  export type RozenitePluginOptions = {
81
17
  /**
@@ -86,24 +22,14 @@ export type RozenitePluginOptions = {
86
22
  tailwind?: boolean;
87
23
  };
88
24
 
25
+ /**
26
+ * Builds the DevTools panels of a Rozenite plugin.
27
+ *
28
+ * Panels are the only part of a plugin that needs bundling: they ship as a
29
+ * browser bundle rather than as a resolvable package entry point. The React
30
+ * Native, Metro and SDK entry points are compiled with tsc by `rozenite build`.
31
+ */
89
32
  export const rozenitePlugin = ({ tailwind = true }: RozenitePluginOptions = {}): PluginOption[] => {
90
- const isServer = process.env.VITE_ROZENITE_TARGET === 'server';
91
- const isReactNative = process.env.VITE_ROZENITE_TARGET === 'react-native';
92
- const isSdk = process.env.VITE_ROZENITE_TARGET === 'sdk';
93
-
94
- if (isServer) {
95
- return [rozeniteServerPlugin(), getDtsPlugin('metro')] as PluginOption[];
96
- } else if (isReactNative) {
97
- return [
98
- react(),
99
- requirePlugin(),
100
- rozeniteReactNativePlugin(),
101
- getDtsPlugin('react-native'),
102
- ] as PluginOption[];
103
- } else if (isSdk) {
104
- return [rozeniteSdkPlugin(), getDtsPlugin('sdk')] as PluginOption[];
105
- }
106
-
107
33
  return [
108
34
  ...(tailwind ? [tailwindcss()] : []),
109
35
  react(),
@@ -1,6 +1,12 @@
1
1
  import { transformWithEsbuild } from 'vite';
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
+ import {
5
+ type RozeniteIntegration,
6
+ ROZENITE_INTEGRATIONS,
7
+ DEFAULT_PLUGIN_INTEGRATIONS,
8
+ isRozeniteIntegration,
9
+ } from '@rozenite/tools';
4
10
 
5
11
  export type PanelEntry = {
6
12
  name: string;
@@ -64,6 +70,44 @@ export type DevConfig = {
64
70
  export type RozeniteConfig = {
65
71
  panels: PanelEntry[];
66
72
  dev?: DevConfig;
73
+ /**
74
+ * The target environments this plugin supports. Reported in the plugin
75
+ * manifest (`rozenite.json`) so a consumer can refuse to load a plugin on
76
+ * an integration it doesn't declare, instead of loading it and breaking.
77
+ *
78
+ * @default ['react-native']
79
+ */
80
+ integrations?: RozeniteIntegration[];
81
+ };
82
+
83
+ /**
84
+ * Validates a plugin's declared `integrations` and returns the resolved
85
+ * list — the declaration, or the default when omitted — so the manifest
86
+ * always answers "which integrations does this plugin support", even for a
87
+ * plugin authored before this field existed.
88
+ *
89
+ * Called eagerly from `client-plugin.ts`'s `config()` hook, right after the
90
+ * config is loaded: a typo here must fail the build loudly rather than
91
+ * silently making the plugin unloadable everywhere that later checks it.
92
+ */
93
+ export const resolveIntegrations = (config: RozeniteConfig): RozeniteIntegration[] => {
94
+ const integrations = config.integrations ?? DEFAULT_PLUGIN_INTEGRATIONS;
95
+
96
+ if (integrations.length === 0) {
97
+ throw new Error(
98
+ `"integrations" in rozenite.config.ts must not be empty. Valid ids: ${ROZENITE_INTEGRATIONS.join(', ')}.`,
99
+ );
100
+ }
101
+
102
+ for (const integration of integrations) {
103
+ if (!isRozeniteIntegration(integration)) {
104
+ throw new Error(
105
+ `Invalid integration "${integration}" in rozenite.config.ts. Valid ids: ${ROZENITE_INTEGRATIONS.join(', ')}.`,
106
+ );
107
+ }
108
+ }
109
+
110
+ return [...integrations];
67
111
  };
68
112
 
69
113
  export const loadConfig = async (configPath: string): Promise<RozeniteConfig> => {
@@ -1,5 +0,0 @@
1
- type Target = 'react-native' | 'metro' | 'sdk';
2
- export declare const normalizeRolledUpDeclarations: (content: string) => string;
3
- export declare const bundleTargetDeclarations: (projectRoot: string, target: Target) => Promise<void>;
4
- export {};
5
- //# sourceMappingURL=bundle-dts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundle-dts.d.ts","sourceRoot":"","sources":["../src/bundle-dts.ts"],"names":[],"mappings":"AAIA,KAAK,MAAM,GAAG,cAAc,GAAG,OAAO,GAAG,KAAK,CAAC;AAE/C,eAAO,MAAM,6BAA6B,GAAI,SAAS,MAAM,WA2B5D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,aAAa,MAAM,EAAE,QAAQ,MAAM,kBAiFjF,CAAC"}