@wealthfolio/addon-dev-tools 3.6.2 → 3.7.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 CHANGED
@@ -47,6 +47,11 @@ wealthfolio-addon test
47
47
 
48
48
  ## Development Server
49
49
 
50
+ > **Version compatibility:** Wealthfolio 3.7 requires
51
+ > `@wealthfolio/addon-dev-tools` 3.7 or newer. If the app reports that the
52
+ > server does not support v3.7 runtime packages, update this package and restart
53
+ > the development server.
54
+
50
55
  The development server provides:
51
56
 
52
57
  - Hot reload functionality
@@ -57,12 +62,37 @@ The development server provides:
57
62
  ### API Endpoints
58
63
 
59
64
  - `GET /health` - Health check
60
- - `GET /status` - Addon status and last modified time
65
+ - `GET /status` - Build state and published runtime-package generation
61
66
  - `GET /manifest.json` - Addon manifest
62
67
  - `GET /addon.js` - Built addon code
68
+ - `GET /runtime-package` - One coherent manifest, code, and asset-metadata
69
+ snapshot
70
+ - `GET /runtime-files` - Built JavaScript and CSS modules
71
+ - `GET /runtime-assets` - Packaged asset metadata
72
+ - `GET /runtime-assets/:assetId?generation=<id>` - One asset from a published
73
+ generation
63
74
  - `GET /files` - List of built files
64
75
  - `GET /test` - Test connectivity
65
76
 
77
+ The host loads `/runtime-package` first, then requests asset bytes from the same
78
+ generation. The server retains the four most recent immutable generations so a
79
+ reload cannot mix new metadata with old bytes. A generation older than that
80
+ window is intentionally unavailable and the host must load the current package
81
+ snapshot again. `/manifest.json` and `/addon.js` remain diagnostic/legacy
82
+ endpoints; Wealthfolio 3.7 live loading does not assemble a runtime from them.
83
+
84
+ Files below `assets/**` and non-code files below `dist/assets/**` are published
85
+ as private asset metadata and lazy byte responses. JavaScript and CSS remain in
86
+ `/runtime-files`. The same 256-entry, 5 MiB-per-file, and 25 MiB-package limits
87
+ used during installation are enforced during development.
88
+
89
+ Generated projects pin `build.target` to Chrome/Edge 107, Firefox 104, and
90
+ Safari 16, matching Wealthfolio 3.7. Keep that explicit target when customizing
91
+ Vite so a future Vite default cannot silently raise the addon's browser floor.
92
+ The sandbox supports packaged images, fonts, media, CSS, and WebAssembly, but
93
+ does not allow Worker/service-worker entry points, popups, direct network
94
+ requests, or remote CSS imports.
95
+
66
96
  ## Usage in Addon Projects
67
97
 
68
98
  Add to your addon's `package.json`:
@@ -73,7 +103,7 @@ Add to your addon's `package.json`:
73
103
  "dev:server": "wealthfolio-addon dev"
74
104
  },
75
105
  "devDependencies": {
76
- "@wealthfolio/addon-dev-tools": "^1.0.0"
106
+ "@wealthfolio/addon-dev-tools": "^3.7.0"
77
107
  }
78
108
  }
79
109
  ```
package/dev-server.js CHANGED
@@ -15,8 +15,219 @@ const path = require("path");
15
15
  const fs = require("fs");
16
16
  const { exec } = require("child_process");
17
17
  const { promisify } = require("util");
18
+ const { createHash } = require("crypto");
18
19
 
19
20
  const execAsync = promisify(exec);
21
+ const MAX_RUNTIME_PACKAGE_ENTRIES = 256;
22
+ const MAX_RUNTIME_ASSET_FILE_SIZE = 5 * 1024 * 1024;
23
+ const MAX_RUNTIME_ASSET_TOTAL_SIZE = 25 * 1024 * 1024;
24
+ const MAX_RUNTIME_PACKAGE_GENERATIONS = 4;
25
+
26
+ function runtimeAssetMimeType(filePath) {
27
+ const extension = path.extname(filePath).toLowerCase();
28
+ return (
29
+ {
30
+ ".avif": "image/avif",
31
+ ".bmp": "image/bmp",
32
+ ".css": "text/css",
33
+ ".csv": "text/csv",
34
+ ".gif": "image/gif",
35
+ ".html": "text/html",
36
+ ".ico": "image/x-icon",
37
+ ".jpeg": "image/jpeg",
38
+ ".jpg": "image/jpeg",
39
+ ".json": "application/json",
40
+ ".md": "text/markdown",
41
+ ".mp3": "audio/mpeg",
42
+ ".mp4": "video/mp4",
43
+ ".ogg": "audio/ogg",
44
+ ".otf": "font/otf",
45
+ ".pdf": "application/pdf",
46
+ ".png": "image/png",
47
+ ".svg": "image/svg+xml",
48
+ ".ttf": "font/ttf",
49
+ ".txt": "text/plain",
50
+ ".wasm": "application/wasm",
51
+ ".wav": "audio/wav",
52
+ ".webm": "video/webm",
53
+ ".webp": "image/webp",
54
+ ".woff": "font/woff",
55
+ ".woff2": "font/woff2",
56
+ ".xml": "application/xml",
57
+ }[extension] || "application/octet-stream"
58
+ );
59
+ }
60
+
61
+ function walkRuntimeFiles(rootPath, visit) {
62
+ if (!fs.existsSync(rootPath)) return;
63
+ const rootStats = fs.lstatSync(rootPath);
64
+ if (rootStats.isSymbolicLink() || !rootStats.isDirectory()) {
65
+ throw new Error(`Runtime asset root is not a regular directory: ${rootPath}`);
66
+ }
67
+ for (const entry of fs.readdirSync(rootPath, { withFileTypes: true })) {
68
+ const entryPath = path.join(rootPath, entry.name);
69
+ if (entry.isSymbolicLink()) {
70
+ throw new Error(`Runtime assets cannot contain symbolic links: ${entryPath}`);
71
+ }
72
+ if (entry.isDirectory()) {
73
+ walkRuntimeFiles(entryPath, visit);
74
+ } else if (entry.isFile()) {
75
+ visit(entryPath);
76
+ }
77
+ }
78
+ }
79
+
80
+ function getRuntimePackageEntries(addonPath) {
81
+ const entries = [];
82
+ let totalSize = 0;
83
+ for (const root of [path.join(addonPath, "dist"), path.join(addonPath, "assets")]) {
84
+ walkRuntimeFiles(root, (filePath) => {
85
+ const logicalPath = path.relative(addonPath, filePath).split(path.sep).join("/");
86
+ const extension = path.extname(filePath).toLowerCase();
87
+ if (extension === ".map") return;
88
+ if ([".gitkeep", ".ds_store"].includes(path.basename(filePath).toLowerCase())) return;
89
+ const kind =
90
+ extension === ".js" || extension === ".css"
91
+ ? "text"
92
+ : logicalPath.startsWith("assets/") || logicalPath.startsWith("dist/assets/")
93
+ ? "asset"
94
+ : undefined;
95
+ if (!kind) return;
96
+ if (entries.length >= MAX_RUNTIME_PACKAGE_ENTRIES) {
97
+ throw new Error("Runtime package contains more than 256 files");
98
+ }
99
+ const size = fs.statSync(filePath).size;
100
+ if (size > MAX_RUNTIME_ASSET_FILE_SIZE) {
101
+ throw new Error(`Runtime package file exceeds 5 MiB: ${filePath}`);
102
+ }
103
+ totalSize += size;
104
+ if (totalSize > MAX_RUNTIME_ASSET_TOTAL_SIZE) {
105
+ throw new Error("Runtime package exceeds the 25 MiB package limit");
106
+ }
107
+ entries.push({ extension, filePath, kind, logicalPath, size });
108
+ });
109
+ }
110
+ return entries.sort((left, right) => left.logicalPath.localeCompare(right.logicalPath));
111
+ }
112
+
113
+ function runtimeAssetId(logicalPath, content) {
114
+ return createHash("sha256").update(logicalPath).update("\0").update(content).digest("hex");
115
+ }
116
+
117
+ function readRuntimeEntry(entry) {
118
+ const content = fs.readFileSync(entry.filePath);
119
+ if (content.length !== entry.size) {
120
+ throw new Error(`Runtime package changed while it was being indexed: ${entry.filePath}`);
121
+ }
122
+ return content;
123
+ }
124
+
125
+ function runtimeAssetsFromEntries(entries) {
126
+ return entries
127
+ .filter((entry) => entry.kind === "asset")
128
+ .map((entry) => {
129
+ const content = readRuntimeEntry(entry);
130
+ return {
131
+ content,
132
+ descriptor: {
133
+ id: runtimeAssetId(entry.logicalPath, content),
134
+ mimeType: runtimeAssetMimeType(entry.filePath),
135
+ path: entry.logicalPath,
136
+ size: content.length,
137
+ },
138
+ };
139
+ });
140
+ }
141
+
142
+ function runtimeTextFilesFromEntries(entries) {
143
+ return entries
144
+ .filter((entry) => entry.kind === "text")
145
+ .map((entry) => ({
146
+ content: readRuntimeEntry(entry).toString("utf8"),
147
+ isMain: entry.logicalPath === "dist/addon.js",
148
+ name: entry.logicalPath,
149
+ }));
150
+ }
151
+
152
+ function getRuntimeAssets(addonPath) {
153
+ return runtimeAssetsFromEntries(getRuntimePackageEntries(addonPath));
154
+ }
155
+
156
+ function getRuntimeTextFiles(addonPath) {
157
+ return runtimeTextFilesFromEntries(getRuntimePackageEntries(addonPath));
158
+ }
159
+
160
+ function shouldPublishRuntimeFileChange(config, filePath) {
161
+ if (!config.buildCommand) return true;
162
+ const absolutePath = path.resolve(filePath);
163
+ const manifestPath = path.resolve(config.manifestPath);
164
+ const assetRoot = path.resolve(config.addonPath, "assets");
165
+ return absolutePath === manifestPath || absolutePath.startsWith(`${assetRoot}${path.sep}`);
166
+ }
167
+
168
+ class RuntimePackageRegistry {
169
+ constructor(addonPath, manifestPath = path.join(addonPath, "manifest.json")) {
170
+ this.addonPath = addonPath;
171
+ this.manifestPath = manifestPath;
172
+ this.currentGeneration = 0;
173
+ this.snapshots = new Map();
174
+ }
175
+
176
+ createSnapshot() {
177
+ const entries = getRuntimePackageEntries(this.addonPath);
178
+ const manifest = fs.existsSync(this.manifestPath)
179
+ ? JSON.parse(fs.readFileSync(this.manifestPath, "utf8"))
180
+ : null;
181
+ return {
182
+ assets: runtimeAssetsFromEntries(entries),
183
+ files: runtimeTextFilesFromEntries(entries),
184
+ manifest,
185
+ };
186
+ }
187
+
188
+ refresh() {
189
+ const generation = this.currentGeneration + 1;
190
+ const snapshot = { generation, ...this.createSnapshot() };
191
+ this.snapshots.set(generation, snapshot);
192
+ this.currentGeneration = generation;
193
+ while (this.snapshots.size > MAX_RUNTIME_PACKAGE_GENERATIONS) {
194
+ this.snapshots.delete(this.snapshots.keys().next().value);
195
+ }
196
+ return snapshot;
197
+ }
198
+
199
+ getSnapshot(generation) {
200
+ if (this.currentGeneration === 0) {
201
+ throw new Error("No runtime package generation has been published");
202
+ }
203
+ const requestedGeneration = generation ?? this.currentGeneration;
204
+ const snapshot = this.snapshots.get(requestedGeneration);
205
+ if (!snapshot) {
206
+ throw new Error(`Runtime package generation is no longer available: ${requestedGeneration}`);
207
+ }
208
+ return snapshot;
209
+ }
210
+
211
+ getPackage(generation) {
212
+ return this.getSnapshot(generation);
213
+ }
214
+
215
+ getAssets(generation) {
216
+ return this.getSnapshot(generation).assets;
217
+ }
218
+
219
+ getTextFiles(generation) {
220
+ return this.getSnapshot(generation).files;
221
+ }
222
+
223
+ getManifest(generation) {
224
+ return this.getSnapshot(generation).manifest;
225
+ }
226
+
227
+ getGeneration() {
228
+ return this.currentGeneration;
229
+ }
230
+ }
20
231
 
21
232
  class AddonDevServer {
22
233
  constructor(config) {
@@ -25,9 +236,16 @@ class AddonDevServer {
25
236
  this.lastModified = new Date();
26
237
  this.buildInProgress = false;
27
238
  this.viteWatcher = null;
239
+ this.publishTimer = null;
240
+ this.runtimePackageRegistry = new RuntimePackageRegistry(
241
+ this.config.addonPath,
242
+ this.config.manifestPath,
243
+ );
244
+ this.publishRuntimePackage();
28
245
 
29
246
  this.setupMiddleware();
30
247
  this.setupRoutes();
248
+ this.app.use(express.static(this.config.addonPath));
31
249
  this.setupFileWatcher();
32
250
  this.startViteWatcher();
33
251
  }
@@ -39,7 +257,6 @@ class AddonDevServer {
39
257
  credentials: true,
40
258
  }),
41
259
  );
42
- this.app.use(express.static(this.config.addonPath));
43
260
  }
44
261
 
45
262
  setupRoutes() {
@@ -57,6 +274,7 @@ class AddonDevServer {
57
274
  res.json({
58
275
  lastModified: this.lastModified.toISOString(),
59
276
  buildInProgress: this.buildInProgress,
277
+ generation: this.runtimePackageRegistry.getGeneration(),
60
278
  files: this.getFileList(),
61
279
  });
62
280
  });
@@ -64,9 +282,8 @@ class AddonDevServer {
64
282
  // Serve addon manifest
65
283
  this.app.get("/manifest.json", (req, res) => {
66
284
  try {
67
- const manifestPath = path.resolve(this.config.manifestPath);
68
- if (fs.existsSync(manifestPath)) {
69
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
285
+ const manifest = this.runtimePackageRegistry.getManifest();
286
+ if (manifest) {
70
287
  res.json(manifest);
71
288
  } else {
72
289
  res.status(404).json({ error: "Manifest not found" });
@@ -86,7 +303,13 @@ class AddonDevServer {
86
303
  const fileExists = await this.waitForFile(addonFile, 3000);
87
304
 
88
305
  if (fileExists) {
89
- const code = fs.readFileSync(addonFile, "utf-8");
306
+ const code = this.runtimePackageRegistry
307
+ .getTextFiles()
308
+ .find((file) => file.isMain)?.content;
309
+ if (code === undefined) {
310
+ res.status(404).json({ error: "Addon entry point is not in the published package" });
311
+ return;
312
+ }
90
313
  res.type("application/javascript").send(code);
91
314
  } else {
92
315
  console.error(`❌ Addon file not found at: ${addonFile}`);
@@ -100,6 +323,70 @@ class AddonDevServer {
100
323
  }
101
324
  });
102
325
 
326
+ this.app.get("/runtime-package", (req, res) => {
327
+ try {
328
+ const runtimePackage = this.runtimePackageRegistry.getPackage();
329
+ const mainFile = runtimePackage.files.find((file) => file.isMain);
330
+ if (!mainFile) {
331
+ res.status(409).json({ error: "Runtime package has no published addon entry point" });
332
+ return;
333
+ }
334
+ res.set("Cache-Control", "no-store");
335
+ res.json({
336
+ assets: runtimePackage.assets.map((asset) => asset.descriptor),
337
+ files: runtimePackage.files,
338
+ generation: runtimePackage.generation,
339
+ manifest: runtimePackage.manifest,
340
+ });
341
+ } catch (error) {
342
+ res.status(500).json({ error: error.message });
343
+ }
344
+ });
345
+
346
+ // Metadata-only package registry used by the opaque iframe asset broker.
347
+ this.app.get("/runtime-assets", (req, res) => {
348
+ try {
349
+ res.set("Cache-Control", "no-store");
350
+ res.json(this.runtimePackageRegistry.getAssets().map((asset) => asset.descriptor));
351
+ } catch (error) {
352
+ res.status(500).json({ error: error.message });
353
+ }
354
+ });
355
+
356
+ this.app.get("/runtime-assets/:assetId", (req, res) => {
357
+ try {
358
+ const requestedGeneration =
359
+ req.query.generation === undefined ? undefined : Number(req.query.generation);
360
+ if (
361
+ requestedGeneration !== undefined &&
362
+ (!Number.isSafeInteger(requestedGeneration) || requestedGeneration < 1)
363
+ ) {
364
+ res.status(400).json({ error: "Invalid runtime package generation" });
365
+ return;
366
+ }
367
+ const asset = this.runtimePackageRegistry
368
+ .getAssets(requestedGeneration)
369
+ .find((candidate) => candidate.descriptor.id === req.params.assetId);
370
+ if (!asset) {
371
+ res.status(404).json({ error: "Runtime asset not found" });
372
+ return;
373
+ }
374
+ res.set("Cache-Control", "no-store");
375
+ res.type(asset.descriptor.mimeType).send(asset.content);
376
+ } catch (error) {
377
+ res.status(500).json({ error: error.message });
378
+ }
379
+ });
380
+
381
+ this.app.get("/runtime-files", (req, res) => {
382
+ try {
383
+ res.set("Cache-Control", "no-store");
384
+ res.json(this.runtimePackageRegistry.getTextFiles());
385
+ } catch (error) {
386
+ res.status(500).json({ error: error.message });
387
+ }
388
+ });
389
+
103
390
  // Hot reload endpoint
104
391
  this.app.get("/reload", (req, res) => {
105
392
  res.json({
@@ -167,24 +454,57 @@ class AddonDevServer {
167
454
 
168
455
  watcher.on("change", (filePath) => {
169
456
  console.log(`📝 File changed: ${filePath}`);
170
- // Don't trigger manual build since Vite is already watching
171
- // Just update the timestamp for status endpoint
172
- this.lastModified = new Date();
457
+ this.handleRuntimeFileChange(filePath);
173
458
  });
174
459
 
175
460
  watcher.on("add", (filePath) => {
176
461
  console.log(`➕ File added: ${filePath}`);
177
- this.lastModified = new Date();
462
+ this.handleRuntimeFileChange(filePath);
178
463
  });
179
464
 
180
465
  watcher.on("unlink", (filePath) => {
181
466
  console.log(`➖ File removed: ${filePath}`);
182
- this.lastModified = new Date();
467
+ this.handleRuntimeFileChange(filePath);
183
468
  });
184
469
 
185
470
  console.log(`👀 Watching files: ${this.config.watchPaths.join(", ")}`);
186
471
  }
187
472
 
473
+ handleRuntimeFileChange(filePath) {
474
+ // Source and dist events are intermediate Vite states. The Vite completion
475
+ // signal publishes their next coherent package generation.
476
+ if (shouldPublishRuntimeFileChange(this.config, filePath)) {
477
+ this.scheduleRuntimePackagePublish();
478
+ }
479
+ }
480
+
481
+ scheduleRuntimePackagePublish() {
482
+ if (this.publishTimer) clearTimeout(this.publishTimer);
483
+ this.publishTimer = setTimeout(() => {
484
+ this.publishTimer = null;
485
+ if (this.buildInProgress) {
486
+ this.scheduleRuntimePackagePublish();
487
+ return;
488
+ }
489
+ this.publishRuntimePackage();
490
+ }, 100);
491
+ }
492
+
493
+ publishRuntimePackage() {
494
+ if (this.publishTimer) {
495
+ clearTimeout(this.publishTimer);
496
+ this.publishTimer = null;
497
+ }
498
+ try {
499
+ const runtimePackage = this.runtimePackageRegistry.refresh();
500
+ this.lastModified = new Date();
501
+ return runtimePackage;
502
+ } catch (error) {
503
+ console.error("❌ Failed to publish runtime package:", error);
504
+ return null;
505
+ }
506
+ }
507
+
188
508
  async triggerBuild() {
189
509
  if (this.buildInProgress || !this.config.buildCommand) return;
190
510
 
@@ -197,7 +517,7 @@ class AddonDevServer {
197
517
  });
198
518
 
199
519
  console.log("✅ Build completed successfully");
200
- this.lastModified = new Date();
520
+ this.publishRuntimePackage();
201
521
  } catch (error) {
202
522
  console.error("❌ Build failed:", error);
203
523
  } finally {
@@ -277,7 +597,7 @@ class AddonDevServer {
277
597
 
278
598
  if (output.includes("built in")) {
279
599
  console.log(`✅ Vite rebuild completed`);
280
- this.lastModified = new Date();
600
+ this.publishRuntimePackage();
281
601
  this.buildInProgress = false;
282
602
  }
283
603
 
@@ -325,6 +645,10 @@ class AddonDevServer {
325
645
  stop() {
326
646
  console.log("🛑 Shutting down dev server...");
327
647
 
648
+ if (this.publishTimer) {
649
+ clearTimeout(this.publishTimer);
650
+ this.publishTimer = null;
651
+ }
328
652
  if (this.viteWatcher) {
329
653
  this.viteWatcher.kill("SIGTERM");
330
654
  this.viteWatcher = null;
@@ -343,7 +667,12 @@ function main() {
343
667
  addonPath: path.resolve(addonPath),
344
668
  manifestPath: path.resolve(addonPath, "manifest.json"),
345
669
  buildCommand: "pnpm run build",
346
- watchPaths: [path.resolve(addonPath, "src"), path.resolve(addonPath, "manifest.json")],
670
+ watchPaths: [
671
+ path.resolve(addonPath, "src"),
672
+ path.resolve(addonPath, "assets"),
673
+ path.resolve(addonPath, "dist"),
674
+ path.resolve(addonPath, "manifest.json"),
675
+ ],
347
676
  };
348
677
 
349
678
  // Check if addon directory exists
@@ -363,7 +692,13 @@ function main() {
363
692
  }
364
693
 
365
694
  // Export for use as a module
366
- module.exports = { AddonDevServer };
695
+ module.exports = {
696
+ AddonDevServer,
697
+ RuntimePackageRegistry,
698
+ getRuntimeAssets,
699
+ getRuntimeTextFiles,
700
+ shouldPublishRuntimeFileChange,
701
+ };
367
702
 
368
703
  // Run if called directly
369
704
  if (require.main === module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthfolio/addon-dev-tools",
3
- "version": "3.6.2",
3
+ "version": "3.7.0",
4
4
  "description": "Development tools for Wealthfolio addons - hot reload server and CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "scripts": {
40
- "test": "echo \"No tests yet\" && exit 0"
40
+ "test": "node --test dev-server.test.js scaffold.test.js"
41
41
  },
42
42
  "dependencies": {
43
43
  "chokidar": "^4.0.3",
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
25
25
  ### Added
26
26
  - Initial release of {{addonName}} addon
27
27
  - Basic addon functionality and core features
28
- - Integration with Wealthfolio addon SDK v1.0.0
28
+ - Integration with Wealthfolio addon SDK v3.7.0
29
29
  - Sidebar navigation integration for easy access
30
30
  - Responsive design for all screen sizes
31
31
 
@@ -34,5 +34,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
34
  - User-friendly interface
35
35
  - Compatible with Wealthfolio platform
36
36
 
37
- ### Permissions
38
- - UI components access for sidebar and routing
37
+ ### Compatibility
38
+ - Requires Wealthfolio 3.7.0 or newer
@@ -18,6 +18,14 @@ npm run build
18
18
  npm run bundle
19
19
  ```
20
20
 
21
+ Wealthfolio 3.7 indexes private files below `assets/` and `dist/assets/` automatically. Use
22
+ `ctx.assets.getBlob(path)` or `ctx.assets.getUrl(path)` to load them in the sandbox; no manifest
23
+ asset list or permission is required. Blob URLs are valid only for the current addon lifetime.
24
+
25
+ The generated Vite config targets Chrome/Edge 107, Firefox 104, and Safari 16, matching Wealthfolio
26
+ 3.7. The sandbox supports packaged images, fonts, media, CSS, and WebAssembly. Worker/service-worker
27
+ entry points, popups, direct network requests, and remote CSS imports are intentionally unavailable.
28
+
21
29
  ## Features
22
30
 
23
31
  - Add your features here
@@ -26,7 +26,8 @@ function AddonExample({ ctx }: { ctx: AddonContext }) {
26
26
  // Route component. The sidebar entry + route are declared in manifest.json
27
27
  // (`contributes.routes` + `contributes.links`), so the host renders navigation
28
28
  // without booting the addon; this component only runs when the route is first
29
- // visited. The QueryClientProvider shares one cache across route navigations.
29
+ // visited. The QueryClientProvider reuses this addon's isolated cache across
30
+ // route navigations; invalidations/refetches are bridged to the host.
30
31
  const AddonRoute = () => (
31
32
  <QueryClientProvider client={addonCtx!.api.query.getClient() as QueryClient}>
32
33
  <AddonExample ctx={addonCtx!} />
@@ -5,8 +5,8 @@
5
5
  "description": "{{description}}",
6
6
  "author": "{{author}}",
7
7
  "main": "dist/addon.js",
8
- "sdkVersion": "3.6.2",
9
- "minWealthfolioVersion": "3.6.2",
8
+ "sdkVersion": "3.7.0",
9
+ "minWealthfolioVersion": "3.7.0",
10
10
  "enabled": true,
11
11
  "contributes": {
12
12
  "routes": [{ "id": "{{addonId}}" }],
@@ -24,8 +24,8 @@
24
24
  },
25
25
  "hostDependencies": {
26
26
  "@tanstack/react-query": "^5.90.0",
27
- "@wealthfolio/addon-sdk": "^3.6.2",
28
- "@wealthfolio/ui": "^3.6.0",
27
+ "@wealthfolio/addon-sdk": "^3.7.0",
28
+ "@wealthfolio/ui": "^3.7.0",
29
29
  "date-fns": "^4.1.0",
30
30
  "lucide-react": "^0.561.0",
31
31
  "react": "^19.2.0",
@@ -9,17 +9,17 @@
9
9
  "scripts": {
10
10
  "build": "vite build",
11
11
  "dev": "vite build --watch",
12
- "dev:server": "wealthfolio dev",
12
+ "dev:server": "wealthfolio-addon dev",
13
13
  "clean": "rm -rf dist",
14
- "package": "mkdir -p dist && find dist -name '*.map' -delete && zip -r dist/$npm_package_name-$npm_package_version.zip manifest.json dist/ assets/ README.md -x '*.map'",
14
+ "package": "mkdir -p dist assets && find dist -name '*.map' -delete && zip -r dist/$npm_package_name-$npm_package_version.zip manifest.json dist/ assets/ README.md -x '*.map'",
15
15
  "bundle": "pnpm clean && pnpm build && pnpm package",
16
16
  "lint": "tsc --noEmit",
17
17
  "type-check": "tsc --noEmit"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@tanstack/react-query": "^5.90.0",
21
- "@wealthfolio/addon-sdk": "^3.6.2",
22
- "@wealthfolio/ui": "^3.6.0",
21
+ "@wealthfolio/addon-sdk": "^3.7.0",
22
+ "@wealthfolio/ui": "^3.7.0",
23
23
  "date-fns": "^4.1.0",
24
24
  "lucide-react": "^0.561.0",
25
25
  "react": "^19.2.0",
@@ -29,9 +29,9 @@
29
29
  "devDependencies": {
30
30
  "@tanstack/react-query": "^5.90.20",
31
31
  "@tailwindcss/vite": "^4.1.13",
32
- "@wealthfolio/addon-dev-tools": "^3.6.0",
33
- "@wealthfolio/addon-sdk": "^3.6.2",
34
- "@wealthfolio/ui": "^3.6.0",
32
+ "@wealthfolio/addon-dev-tools": "^3.7.0",
33
+ "@wealthfolio/addon-sdk": "^3.7.0",
34
+ "@wealthfolio/ui": "^3.7.0",
35
35
  "@types/node": "^20.0.0",
36
36
  "@types/react": "^19.2.13",
37
37
  "@types/react-dom": "^19.2.3",
@@ -1,42 +1,43 @@
1
- import react from '@vitejs/plugin-react';
2
- import tailwindcss from '@tailwindcss/vite';
3
- import { defineConfig } from 'vite';
1
+ import react from "@vitejs/plugin-react";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import { defineConfig } from "vite";
4
4
 
5
5
  const hostProvidedDependencies = [
6
- '@tanstack/react-query',
7
- '@wealthfolio/addon-sdk',
8
- '@wealthfolio/addon-sdk/goal-progress',
9
- '@wealthfolio/addon-sdk/host-api',
10
- '@wealthfolio/addon-sdk/host-dependencies',
11
- '@wealthfolio/addon-sdk/manifest',
12
- '@wealthfolio/addon-sdk/permissions',
13
- '@wealthfolio/addon-sdk/query-keys',
14
- '@wealthfolio/addon-sdk/types',
15
- '@wealthfolio/addon-sdk/utils',
16
- '@wealthfolio/ui',
17
- '@wealthfolio/ui/chart',
18
- 'date-fns',
19
- 'lucide-react',
20
- 'react',
21
- 'react-dom',
22
- 'react-dom/client',
23
- 'react/jsx-dev-runtime',
24
- 'react/jsx-runtime',
25
- 'recharts',
6
+ "@tanstack/react-query",
7
+ "@wealthfolio/addon-sdk",
8
+ "@wealthfolio/addon-sdk/goal-progress",
9
+ "@wealthfolio/addon-sdk/host-api",
10
+ "@wealthfolio/addon-sdk/host-dependencies",
11
+ "@wealthfolio/addon-sdk/manifest",
12
+ "@wealthfolio/addon-sdk/permissions",
13
+ "@wealthfolio/addon-sdk/query-keys",
14
+ "@wealthfolio/addon-sdk/types",
15
+ "@wealthfolio/addon-sdk/utils",
16
+ "@wealthfolio/ui",
17
+ "@wealthfolio/ui/chart",
18
+ "date-fns",
19
+ "lucide-react",
20
+ "react",
21
+ "react-dom",
22
+ "react-dom/client",
23
+ "react/jsx-dev-runtime",
24
+ "react/jsx-runtime",
25
+ "recharts",
26
26
  ];
27
27
 
28
28
  export default defineConfig({
29
29
  plugins: [react(), tailwindcss()],
30
30
  define: {
31
- 'process.env.NODE_ENV': JSON.stringify('production'),
31
+ "process.env.NODE_ENV": JSON.stringify("production"),
32
32
  },
33
33
  build: {
34
+ target: ["chrome107", "edge107", "firefox104", "safari16"],
34
35
  lib: {
35
- entry: 'src/addon.tsx',
36
- fileName: () => 'addon.js',
37
- formats: ['es'],
36
+ entry: "src/addon.tsx",
37
+ fileName: () => "addon.js",
38
+ formats: ["es"],
38
39
  },
39
- outDir: 'dist',
40
+ outDir: "dist",
40
41
  minify: true,
41
42
  sourcemap: false,
42
43
  rollupOptions: {
@@ -44,8 +45,8 @@ export default defineConfig({
44
45
  },
45
46
  watch: {
46
47
  // Watch mode options for better hot reloading
47
- include: ['src/**'],
48
- exclude: ['node_modules/**', 'dist/**']
49
- }
48
+ include: ["src/**"],
49
+ exclude: ["node_modules/**", "dist/**"],
50
+ },
50
51
  },
51
52
  });