@wealthfolio/addon-dev-tools 1.0.0 → 3.0.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
@@ -11,27 +11,32 @@ npm install -g @wealthfolio/addon-dev-tools
11
11
  ## CLI Commands
12
12
 
13
13
  ### Create New Addon
14
+
14
15
  ```bash
15
16
  wealthfolio create my-awesome-addon
16
17
  ```
17
18
 
18
19
  ### Start Development Server
20
+
19
21
  ```bash
20
22
  # In your addon directory
21
23
  wealthfolio dev
22
24
  ```
23
25
 
24
26
  ### Build Addon
27
+
25
28
  ```bash
26
29
  wealthfolio build
27
30
  ```
28
31
 
29
32
  ### Package for Distribution
33
+
30
34
  ```bash
31
35
  wealthfolio package
32
36
  ```
33
37
 
34
38
  ### Test Setup
39
+
35
40
  ```bash
36
41
  wealthfolio test
37
42
  ```
@@ -39,6 +44,7 @@ wealthfolio test
39
44
  ## Development Server
40
45
 
41
46
  The development server provides:
47
+
42
48
  - Hot reload functionality
43
49
  - File watching
44
50
  - Auto-building
@@ -71,8 +77,9 @@ Add to your addon's `package.json`:
71
77
  ## Architecture
72
78
 
73
79
  This package is separate from `@wealthfolio/addon-sdk` to:
80
+
74
81
  - Keep the SDK lightweight for production
75
- - Avoid unnecessary dependencies in addon bundles
82
+ - Avoid unnecessary dependencies in addon bundles
76
83
  - Provide optional development tooling
77
84
 
78
85
  ## License
package/cli.js CHANGED
@@ -2,31 +2,31 @@
2
2
 
3
3
  /**
4
4
  * Wealthfolio Addon CLI
5
- *
5
+ *
6
6
  * Command-line tool for developing, building, and managing addons
7
7
  */
8
8
 
9
- const { program } = require('commander');
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { exec, spawn } = require('child_process');
13
- const { promisify } = require('util');
14
- const readline = require('node:readline/promises');
15
- const { stdin, stdout } = require('node:process');
16
- const { AddonScaffold } = require('./scaffold');
9
+ const { program } = require("commander");
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { exec, spawn } = require("child_process");
13
+ const { promisify } = require("util");
14
+ const readline = require("node:readline/promises");
15
+ const { stdin, stdout } = require("node:process");
16
+ const { AddonScaffold } = require("./scaffold");
17
17
 
18
18
  const execAsync = promisify(exec);
19
19
 
20
20
  // Colors for console output
21
21
  const colors = {
22
- reset: '\x1b[0m',
23
- bright: '\x1b[1m',
24
- red: '\x1b[31m',
25
- green: '\x1b[32m',
26
- yellow: '\x1b[33m',
27
- blue: '\x1b[34m',
28
- magenta: '\x1b[35m',
29
- cyan: '\x1b[36m'
22
+ reset: "\x1b[0m",
23
+ bright: "\x1b[1m",
24
+ red: "\x1b[31m",
25
+ green: "\x1b[32m",
26
+ yellow: "\x1b[33m",
27
+ blue: "\x1b[34m",
28
+ magenta: "\x1b[35m",
29
+ cyan: "\x1b[36m",
30
30
  };
31
31
 
32
32
  function log(message, color = colors.reset) {
@@ -56,19 +56,19 @@ const scaffold = new AddonScaffold();
56
56
  async function createAddon(name, options) {
57
57
  try {
58
58
  info(`Creating new addon: ${name}`);
59
-
59
+
60
60
  // Prepare configuration
61
61
  const config = {
62
62
  name,
63
63
  description: options.description,
64
- author: options.author
64
+ author: options.author,
65
65
  };
66
66
 
67
67
  // Validate configuration
68
68
  const validationErrors = scaffold.validateConfig(config);
69
69
  if (validationErrors.length > 0) {
70
- error('Configuration errors:');
71
- validationErrors.forEach(err => error(` - ${err}`));
70
+ error("Configuration errors:");
71
+ validationErrors.forEach((err) => error(` - ${err}`));
72
72
  return;
73
73
  }
74
74
 
@@ -83,7 +83,7 @@ async function createAddon(name, options) {
83
83
  config.description = answer.length > 0 ? answer : defaultDesc;
84
84
  }
85
85
  if (!config.author) {
86
- const defaultAuthor = 'Anonymous';
86
+ const defaultAuthor = "Anonymous";
87
87
  const answer = (await rl.question(`Author [${defaultAuthor}]: `)).trim();
88
88
  config.author = answer.length > 0 ? answer : defaultAuthor;
89
89
  }
@@ -97,25 +97,25 @@ async function createAddon(name, options) {
97
97
  config.description = `A Wealthfolio addon for ${name}`;
98
98
  }
99
99
  if (!config.author) {
100
- config.author = 'Anonymous';
100
+ config.author = "Anonymous";
101
101
  }
102
-
103
- const addonId = name.toLowerCase().replace(/[^a-z0-9]/g, '-');
102
+
103
+ const addonId = name.toLowerCase().replace(/[^a-z0-9]/g, "-");
104
104
  const addonDir = path.resolve(process.cwd(), addonId);
105
-
105
+
106
106
  // Check if directory already exists
107
107
  if (fs.existsSync(addonDir)) {
108
108
  error(`Directory ${addonId} already exists`);
109
109
  return;
110
110
  }
111
-
111
+
112
112
  // Add current date for changelog
113
- const currentDate = new Date().toISOString().split('T')[0];
113
+ const currentDate = new Date().toISOString().split("T")[0];
114
114
  config.currentDate = currentDate;
115
-
115
+
116
116
  // Create addon using scaffold service
117
117
  const result = await scaffold.createAddon(config, addonDir);
118
-
118
+
119
119
  success(`Addon ${name} created successfully!`);
120
120
  info(`Directory: ${result.addonDir}`);
121
121
  info(`Addon ID: ${result.addonId}`);
@@ -139,7 +139,6 @@ async function createAddon(name, options) {
139
139
  info(` 1. cd ${addonId}`);
140
140
  info(` 2. pnpm install`);
141
141
  info(` 3. pnpm run dev:server`);
142
-
143
142
  } catch (err) {
144
143
  error(`Failed to create addon: ${err.message}`);
145
144
  }
@@ -148,34 +147,33 @@ async function createAddon(name, options) {
148
147
  // Command: dev
149
148
  async function startDev(port = 3001) {
150
149
  try {
151
- const manifestPath = path.resolve(process.cwd(), 'manifest.json');
152
-
150
+ const manifestPath = path.resolve(process.cwd(), "manifest.json");
151
+
153
152
  if (!fs.existsSync(manifestPath)) {
154
- error('No manifest.json found. Are you in an addon directory?');
153
+ error("No manifest.json found. Are you in an addon directory?");
155
154
  return;
156
155
  }
157
-
158
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
159
-
156
+
157
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
158
+
160
159
  info(`Starting development server for ${manifest.name}`);
161
160
  info(`Server will run on http://localhost:${port}`);
162
-
161
+
163
162
  // Start the development server
164
- const devServerPath = path.resolve(__dirname, 'dev-server.js');
165
- const child = spawn('node', [devServerPath, process.cwd(), port.toString()], {
166
- stdio: 'inherit'
163
+ const devServerPath = path.resolve(__dirname, "dev-server.js");
164
+ const child = spawn("node", [devServerPath, process.cwd(), port.toString()], {
165
+ stdio: "inherit",
167
166
  });
168
-
167
+
169
168
  // Handle cleanup on exit
170
- process.on('SIGINT', () => {
171
- child.kill('SIGINT');
169
+ process.on("SIGINT", () => {
170
+ child.kill("SIGINT");
172
171
  process.exit(0);
173
172
  });
174
-
175
- child.on('exit', (code) => {
173
+
174
+ child.on("exit", (code) => {
176
175
  process.exit(code);
177
176
  });
178
-
179
177
  } catch (err) {
180
178
  error(`Failed to start development server: ${err.message}`);
181
179
  }
@@ -184,17 +182,16 @@ async function startDev(port = 3001) {
184
182
  // Command: build
185
183
  async function buildAddon() {
186
184
  try {
187
- info('Building addon...');
188
-
189
- const packageJsonPath = path.resolve(process.cwd(), 'package.json');
185
+ info("Building addon...");
186
+
187
+ const packageJsonPath = path.resolve(process.cwd(), "package.json");
190
188
  if (!fs.existsSync(packageJsonPath)) {
191
- error('No package.json found. Are you in an addon directory?');
189
+ error("No package.json found. Are you in an addon directory?");
192
190
  return;
193
191
  }
194
-
195
- await execAsync('pnpm run build');
196
- success('Addon built successfully!');
197
-
192
+
193
+ await execAsync("pnpm run build");
194
+ success("Addon built successfully!");
198
195
  } catch (err) {
199
196
  error(`Build failed: ${err.message}`);
200
197
  }
@@ -203,15 +200,14 @@ async function buildAddon() {
203
200
  // Command: package
204
201
  async function packageAddon() {
205
202
  try {
206
- info('Packaging addon...');
207
-
203
+ info("Packaging addon...");
204
+
208
205
  // Build first
209
206
  await buildAddon();
210
-
207
+
211
208
  // Create package
212
- await execAsync('pnpm run package');
213
- success('Addon packaged successfully!');
214
-
209
+ await execAsync("pnpm run package");
210
+ success("Addon packaged successfully!");
215
211
  } catch (err) {
216
212
  error(`Packaging failed: ${err.message}`);
217
213
  }
@@ -220,44 +216,43 @@ async function packageAddon() {
220
216
  // Command: test
221
217
  async function testSetup() {
222
218
  try {
223
- info('Testing addon development setup...');
224
-
219
+ info("Testing addon development setup...");
220
+
225
221
  // Check if manifest exists
226
- const manifestPath = path.resolve(process.cwd(), 'manifest.json');
222
+ const manifestPath = path.resolve(process.cwd(), "manifest.json");
227
223
  if (!fs.existsSync(manifestPath)) {
228
- error('❌ No manifest.json found. Are you in an addon directory?');
224
+ error("❌ No manifest.json found. Are you in an addon directory?");
229
225
  return;
230
226
  }
231
-
232
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
227
+
228
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
233
229
  success(`✅ Found manifest for: ${manifest.name}`);
234
-
230
+
235
231
  // Check if dist exists
236
- const distPath = path.resolve(process.cwd(), 'dist');
232
+ const distPath = path.resolve(process.cwd(), "dist");
237
233
  if (!fs.existsSync(distPath)) {
238
- warn('⚠️ No dist directory found. Run `pnpm run build` first.');
234
+ warn("⚠️ No dist directory found. Run `pnpm run build` first.");
239
235
  } else {
240
- success('✅ Dist directory exists');
236
+ success("✅ Dist directory exists");
241
237
  }
242
-
238
+
243
239
  // Check if dev server is running
244
240
  try {
245
- const response = await fetch('http://localhost:3001/test');
241
+ const response = await fetch("http://localhost:3001/test");
246
242
  if (response.ok) {
247
243
  const data = await response.json();
248
- success('✅ Development server is running');
244
+ success("✅ Development server is running");
249
245
  info(` Server message: ${data.message}`);
250
246
  }
251
247
  } catch (error) {
252
- warn('⚠️ Development server not running on port 3001');
253
- info(' Start it with: pnpm run dev:server');
248
+ warn("⚠️ Development server not running on port 3001");
249
+ info(" Start it with: pnpm run dev:server");
254
250
  }
255
-
256
- info('\nNext steps:');
257
- info('1. Start dev server: pnpm run dev:server');
258
- info('2. Start main app in dev mode');
259
- info('3. Check console: discoverAddons()');
260
-
251
+
252
+ info("\nNext steps:");
253
+ info("1. Start dev server: pnpm run dev:server");
254
+ info("2. Start main app in dev mode");
255
+ info("3. Check console: discoverAddons()");
261
256
  } catch (err) {
262
257
  error(`Test failed: ${err.message}`);
263
258
  }
@@ -267,52 +262,36 @@ async function testSetup() {
267
262
  async function installAddon(zipPath) {
268
263
  try {
269
264
  info(`Installing addon from ${zipPath}`);
270
-
265
+
271
266
  // This would integrate with the main app's addon installation
272
- warn('Install command not yet implemented. Use the main app to install.');
273
-
267
+ warn("Install command not yet implemented. Use the main app to install.");
274
268
  } catch (err) {
275
269
  error(`Installation failed: ${err.message}`);
276
270
  }
277
271
  }
278
272
 
279
273
  // CLI Setup
280
- program
281
- .name('wealthfolio')
282
- .description('Wealthfolio Addon Development CLI')
283
- .version('1.0.0');
274
+ program.name("wealthfolio").description("Wealthfolio Addon Development CLI").version("1.0.0");
284
275
 
285
276
  program
286
- .command('create <name>')
287
- .description('Create a new addon')
288
- .option('-d, --description <desc>', 'Addon description')
289
- .option('-a, --author <author>', 'Addon author')
277
+ .command("create <name>")
278
+ .description("Create a new addon")
279
+ .option("-d, --description <desc>", "Addon description")
280
+ .option("-a, --author <author>", "Addon author")
290
281
  .action(createAddon);
291
282
 
292
283
  program
293
- .command('dev')
294
- .description('Start development server')
295
- .option('-p, --port <port>', 'Port number', '3001')
284
+ .command("dev")
285
+ .description("Start development server")
286
+ .option("-p, --port <port>", "Port number", "3001")
296
287
  .action((options) => startDev(parseInt(options.port)));
297
288
 
298
- program
299
- .command('build')
300
- .description('Build the addon')
301
- .action(buildAddon);
289
+ program.command("build").description("Build the addon").action(buildAddon);
302
290
 
303
- program
304
- .command('package')
305
- .description('Package the addon for distribution')
306
- .action(packageAddon);
291
+ program.command("package").description("Package the addon for distribution").action(packageAddon);
307
292
 
308
- program
309
- .command('test')
310
- .description('Test addon development setup')
311
- .action(testSetup);
293
+ program.command("test").description("Test addon development setup").action(testSetup);
312
294
 
313
- program
314
- .command('install <zip>')
315
- .description('Install an addon from zip file')
316
- .action(installAddon);
295
+ program.command("install <zip>").description("Install an addon from zip file").action(installAddon);
317
296
 
318
297
  program.parse();
package/dev-server.js CHANGED
@@ -3,18 +3,18 @@
3
3
 
4
4
  /**
5
5
  * Addon Development Server
6
- *
6
+ *
7
7
  * A simple development server for hot reloading addons during development.
8
8
  * This server watches for file changes and provides a hot reload endpoint.
9
9
  */
10
10
 
11
- const express = require('express');
12
- const cors = require('cors');
13
- const chokidar = require('chokidar');
14
- const path = require('path');
15
- const fs = require('fs');
16
- const { exec } = require('child_process');
17
- const { promisify } = require('util');
11
+ const express = require("express");
12
+ const cors = require("cors");
13
+ const chokidar = require("chokidar");
14
+ const path = require("path");
15
+ const fs = require("fs");
16
+ const { exec } = require("child_process");
17
+ const { promisify } = require("util");
18
18
 
19
19
  const execAsync = promisify(exec);
20
20
 
@@ -25,7 +25,7 @@ class AddonDevServer {
25
25
  this.lastModified = new Date();
26
26
  this.buildInProgress = false;
27
27
  this.viteWatcher = null;
28
-
28
+
29
29
  this.setupMiddleware();
30
30
  this.setupRoutes();
31
31
  this.setupFileWatcher();
@@ -33,76 +33,80 @@ class AddonDevServer {
33
33
  }
34
34
 
35
35
  setupMiddleware() {
36
- this.app.use(cors({
37
- origin: ['http://localhost:1420', 'http://localhost:3000'],
38
- credentials: true
39
- }));
36
+ this.app.use(
37
+ cors({
38
+ origin: ["http://localhost:1420", "http://localhost:3000"],
39
+ credentials: true,
40
+ }),
41
+ );
40
42
  this.app.use(express.static(this.config.addonPath));
41
43
  }
42
44
 
43
45
  setupRoutes() {
44
46
  // Health check endpoint
45
- this.app.get('/health', (req, res) => {
47
+ this.app.get("/health", (req, res) => {
46
48
  res.json({
47
- status: 'ok',
49
+ status: "ok",
48
50
  timestamp: new Date().toISOString(),
49
- addonPath: this.config.addonPath
51
+ addonPath: this.config.addonPath,
50
52
  });
51
53
  });
52
54
 
53
55
  // Addon status endpoint
54
- this.app.get('/status', (req, res) => {
56
+ this.app.get("/status", (req, res) => {
55
57
  res.json({
56
58
  lastModified: this.lastModified.toISOString(),
57
59
  buildInProgress: this.buildInProgress,
58
- files: this.getFileList()
60
+ files: this.getFileList(),
59
61
  });
60
62
  });
61
63
 
62
64
  // Serve addon manifest
63
- this.app.get('/manifest.json', (req, res) => {
65
+ this.app.get("/manifest.json", (req, res) => {
64
66
  try {
65
67
  const manifestPath = path.resolve(this.config.manifestPath);
66
68
  if (fs.existsSync(manifestPath)) {
67
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
69
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
68
70
  res.json(manifest);
69
71
  } else {
70
- res.status(404).json({ error: 'Manifest not found' });
72
+ res.status(404).json({ error: "Manifest not found" });
71
73
  }
72
74
  } catch (error) {
73
- res.status(500).json({ error: 'Failed to read manifest' });
75
+ res.status(500).json({ error: "Failed to read manifest" });
74
76
  }
75
77
  });
76
78
 
77
79
  // Serve addon code
78
- this.app.get('/addon.js', async (req, res) => {
80
+ this.app.get("/addon.js", async (req, res) => {
79
81
  try {
80
- const addonFile = path.resolve(this.config.addonPath, 'dist/addon.js');
82
+ const addonFile = path.resolve(this.config.addonPath, "dist/addon.js");
81
83
  console.log(`📦 Serving addon.js from: ${addonFile}`);
82
-
84
+
83
85
  // Wait for file to exist (with timeout)
84
86
  const fileExists = await this.waitForFile(addonFile, 3000);
85
-
87
+
86
88
  if (fileExists) {
87
- const code = fs.readFileSync(addonFile, 'utf-8');
88
- res.type('application/javascript').send(code);
89
+ const code = fs.readFileSync(addonFile, "utf-8");
90
+ res.type("application/javascript").send(code);
89
91
  } else {
90
92
  console.error(`❌ Addon file not found at: ${addonFile}`);
91
- res.status(404).json({ error: 'Addon file not found. Run build first.', path: addonFile });
93
+ res
94
+ .status(404)
95
+ .json({ error: "Addon file not found. Run build first.", path: addonFile });
92
96
  }
93
97
  } catch (error) {
94
98
  console.error(`❌ Error serving addon.js:`, error);
95
- res.status(500).json({ error: 'Failed to read addon file', details: error.message });
99
+ res.status(500).json({ error: "Failed to read addon file", details: error.message });
96
100
  }
97
101
  });
98
102
 
99
103
  // Hot reload endpoint
100
- this.app.get('/reload', (req, res) => {
104
+ this.app.get("/reload", (req, res) => {
101
105
  res.json({
102
- message: 'Reload triggered',
103
- timestamp: new Date().toISOString()
106
+ message: "Reload triggered",
107
+ timestamp: new Date().toISOString(),
104
108
  });
105
-
109
+
106
110
  // Trigger rebuild if configured
107
111
  if (this.config.buildCommand) {
108
112
  this.triggerBuild();
@@ -110,26 +114,26 @@ class AddonDevServer {
110
114
  });
111
115
 
112
116
  // File listing for debugging
113
- this.app.get('/files', (req, res) => {
117
+ this.app.get("/files", (req, res) => {
114
118
  res.json({
115
119
  files: this.getFileList(),
116
- watchPaths: this.config.watchPaths
120
+ watchPaths: this.config.watchPaths,
117
121
  });
118
122
  });
119
123
 
120
124
  // Test endpoint for connectivity
121
- this.app.get('/test', (req, res) => {
125
+ this.app.get("/test", (req, res) => {
122
126
  res.json({
123
- message: 'Addon development server is working!',
127
+ message: "Addon development server is working!",
124
128
  addonPath: this.config.addonPath,
125
129
  timestamp: new Date().toISOString(),
126
- manifest: this.getManifestInfo()
130
+ manifest: this.getManifestInfo(),
127
131
  });
128
132
  });
129
133
 
130
134
  // Debug endpoint for troubleshooting
131
- this.app.get('/debug', (req, res) => {
132
- const addonFile = path.resolve(this.config.addonPath, 'dist/addon.js');
135
+ this.app.get("/debug", (req, res) => {
136
+ const addonFile = path.resolve(this.config.addonPath, "dist/addon.js");
133
137
  res.json({
134
138
  lastModified: this.lastModified.toISOString(),
135
139
  buildInProgress: this.buildInProgress,
@@ -139,18 +143,18 @@ class AddonDevServer {
139
143
  addonFile: {
140
144
  path: addonFile,
141
145
  exists: fs.existsSync(addonFile),
142
- size: fs.existsSync(addonFile) ? fs.statSync(addonFile).size : 0
146
+ size: fs.existsSync(addonFile) ? fs.statSync(addonFile).size : 0,
143
147
  },
144
148
  config: {
145
149
  port: this.config.port,
146
- buildCommand: this.config.buildCommand
147
- }
150
+ buildCommand: this.config.buildCommand,
151
+ },
148
152
  });
149
153
  });
150
154
 
151
155
  // Simple ping endpoint
152
- this.app.get('/ping', (req, res) => {
153
- res.json({ message: 'pong', timestamp: new Date().toISOString() });
156
+ this.app.get("/ping", (req, res) => {
157
+ res.json({ message: "pong", timestamp: new Date().toISOString() });
154
158
  });
155
159
  }
156
160
 
@@ -158,44 +162,44 @@ class AddonDevServer {
158
162
  const watcher = chokidar.watch(this.config.watchPaths, {
159
163
  ignored: /node_modules|\.git/,
160
164
  persistent: true,
161
- ignoreInitial: true
165
+ ignoreInitial: true,
162
166
  });
163
167
 
164
- watcher.on('change', (filePath) => {
168
+ watcher.on("change", (filePath) => {
165
169
  console.log(`📝 File changed: ${filePath}`);
166
170
  // Don't trigger manual build since Vite is already watching
167
171
  // Just update the timestamp for status endpoint
168
172
  this.lastModified = new Date();
169
173
  });
170
174
 
171
- watcher.on('add', (filePath) => {
175
+ watcher.on("add", (filePath) => {
172
176
  console.log(`➕ File added: ${filePath}`);
173
177
  this.lastModified = new Date();
174
178
  });
175
179
 
176
- watcher.on('unlink', (filePath) => {
180
+ watcher.on("unlink", (filePath) => {
177
181
  console.log(`➖ File removed: ${filePath}`);
178
182
  this.lastModified = new Date();
179
183
  });
180
184
 
181
- console.log(`👀 Watching files: ${this.config.watchPaths.join(', ')}`);
185
+ console.log(`👀 Watching files: ${this.config.watchPaths.join(", ")}`);
182
186
  }
183
187
 
184
188
  async triggerBuild() {
185
189
  if (this.buildInProgress || !this.config.buildCommand) return;
186
-
190
+
187
191
  this.buildInProgress = true;
188
192
  console.log(`🔨 Building addon with: ${this.config.buildCommand}`);
189
-
193
+
190
194
  try {
191
195
  await execAsync(this.config.buildCommand, {
192
- cwd: this.config.addonPath
196
+ cwd: this.config.addonPath,
193
197
  });
194
-
195
- console.log('✅ Build completed successfully');
198
+
199
+ console.log("✅ Build completed successfully");
196
200
  this.lastModified = new Date();
197
201
  } catch (error) {
198
- console.error('❌ Build failed:', error);
202
+ console.error("❌ Build failed:", error);
199
203
  } finally {
200
204
  this.buildInProgress = false;
201
205
  }
@@ -203,9 +207,9 @@ class AddonDevServer {
203
207
 
204
208
  getFileList() {
205
209
  try {
206
- const distPath = path.resolve(this.config.addonPath, 'dist');
210
+ const distPath = path.resolve(this.config.addonPath, "dist");
207
211
  if (fs.existsSync(distPath)) {
208
- return fs.readdirSync(distPath).map(file => `dist/${file}`);
212
+ return fs.readdirSync(distPath).map((file) => `dist/${file}`);
209
213
  }
210
214
  return [];
211
215
  } catch (error) {
@@ -217,7 +221,7 @@ class AddonDevServer {
217
221
  try {
218
222
  const manifestPath = path.resolve(this.config.manifestPath);
219
223
  if (fs.existsSync(manifestPath)) {
220
- return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
224
+ return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
221
225
  }
222
226
  return null;
223
227
  } catch (error) {
@@ -231,7 +235,7 @@ class AddonDevServer {
231
235
  async waitForFile(filePath, timeout = 3000) {
232
236
  const startTime = Date.now();
233
237
  const checkInterval = 100;
234
-
238
+
235
239
  while (Date.now() - startTime < timeout) {
236
240
  if (fs.existsSync(filePath)) {
237
241
  // Additional check to ensure file is fully written
@@ -244,50 +248,50 @@ class AddonDevServer {
244
248
  // File might be in the process of being written
245
249
  }
246
250
  }
247
-
248
- await new Promise(resolve => setTimeout(resolve, checkInterval));
251
+
252
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
249
253
  }
250
-
254
+
251
255
  return false;
252
256
  }
253
257
 
254
258
  startViteWatcher() {
255
259
  if (!this.config.buildCommand) return;
256
-
257
- console.log('🔨 Starting Vite in watch mode...');
258
-
260
+
261
+ console.log("🔨 Starting Vite in watch mode...");
262
+
259
263
  // Start vite build in watch mode
260
- const { spawn } = require('child_process');
261
- this.viteWatcher = spawn('npm', ['run', 'dev'], {
264
+ const { spawn } = require("child_process");
265
+ this.viteWatcher = spawn("pnpm", ["run", "dev"], {
262
266
  cwd: this.config.addonPath,
263
- stdio: ['ignore', 'pipe', 'pipe']
267
+ stdio: ["ignore", "pipe", "pipe"],
264
268
  });
265
-
266
- this.viteWatcher.stdout.on('data', (data) => {
269
+
270
+ this.viteWatcher.stdout.on("data", (data) => {
267
271
  const output = data.toString();
268
272
  console.log(`Vite output: ${output.trim()}`);
269
-
270
- if (output.includes('build started')) {
273
+
274
+ if (output.includes("build started")) {
271
275
  this.buildInProgress = true;
272
276
  }
273
-
274
- if (output.includes('built in')) {
277
+
278
+ if (output.includes("built in")) {
275
279
  console.log(`✅ Vite rebuild completed`);
276
280
  this.lastModified = new Date();
277
281
  this.buildInProgress = false;
278
282
  }
279
-
280
- if (output.includes('watching for file changes')) {
283
+
284
+ if (output.includes("watching for file changes")) {
281
285
  console.log(`✅ Vite watcher ready`);
282
286
  this.buildInProgress = false;
283
287
  }
284
288
  });
285
-
286
- this.viteWatcher.stderr.on('data', (data) => {
289
+
290
+ this.viteWatcher.stderr.on("data", (data) => {
287
291
  console.error(`Vite error: ${data}`);
288
292
  });
289
-
290
- this.viteWatcher.on('close', (code) => {
293
+
294
+ this.viteWatcher.on("close", (code) => {
291
295
  if (code !== 0) {
292
296
  console.error(`Vite watcher exited with code ${code}`);
293
297
  }
@@ -299,30 +303,30 @@ class AddonDevServer {
299
303
  console.log(`🚀 Addon dev server running on http://localhost:${this.config.port}`);
300
304
  console.log(`📁 Serving from: ${this.config.addonPath}`);
301
305
  console.log(`📋 Manifest: ${this.config.manifestPath}`);
302
- console.log(`👀 Watching files: ${this.config.watchPaths.join(', ')}`);
303
-
306
+ console.log(`👀 Watching files: ${this.config.watchPaths.join(", ")}`);
307
+
304
308
  if (this.config.buildCommand) {
305
309
  console.log(`🔨 Build command: ${this.config.buildCommand}`);
306
310
  }
307
311
  });
308
312
 
309
313
  // Handle graceful shutdown
310
- process.on('SIGINT', () => {
314
+ process.on("SIGINT", () => {
311
315
  this.stop();
312
316
  process.exit(0);
313
317
  });
314
318
 
315
- process.on('SIGTERM', () => {
319
+ process.on("SIGTERM", () => {
316
320
  this.stop();
317
321
  process.exit(0);
318
322
  });
319
323
  }
320
324
 
321
325
  stop() {
322
- console.log('🛑 Shutting down dev server...');
323
-
326
+ console.log("🛑 Shutting down dev server...");
327
+
324
328
  if (this.viteWatcher) {
325
- this.viteWatcher.kill('SIGTERM');
329
+ this.viteWatcher.kill("SIGTERM");
326
330
  this.viteWatcher = null;
327
331
  }
328
332
  }
@@ -333,16 +337,13 @@ function main() {
333
337
  const args = process.argv.slice(2);
334
338
  const addonPath = args[0] || process.cwd();
335
339
  const port = parseInt(args[1]) || 3001;
336
-
340
+
337
341
  const config = {
338
342
  port,
339
343
  addonPath: path.resolve(addonPath),
340
- manifestPath: path.resolve(addonPath, 'manifest.json'),
341
- buildCommand: 'npm run build',
342
- watchPaths: [
343
- path.resolve(addonPath, 'src'),
344
- path.resolve(addonPath, 'manifest.json')
345
- ]
344
+ manifestPath: path.resolve(addonPath, "manifest.json"),
345
+ buildCommand: "pnpm run build",
346
+ watchPaths: [path.resolve(addonPath, "src"), path.resolve(addonPath, "manifest.json")],
346
347
  };
347
348
 
348
349
  // Check if addon directory exists
package/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
- AddonDevServer: require('./dev-server').AddonDevServer
2
+ AddonDevServer: require("./dev-server").AddonDevServer,
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthfolio/addon-dev-tools",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Development tools for Wealthfolio addons - hot reload server and CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -14,7 +14,6 @@
14
14
  "hot-reload"
15
15
  ],
16
16
  "author": "Wealthfolio Team",
17
-
18
17
  "license": "MIT",
19
18
  "repository": {
20
19
  "type": "git",
@@ -40,10 +39,10 @@
40
39
  "test": "echo \"No tests yet\" && exit 0"
41
40
  },
42
41
  "dependencies": {
43
- "commander": "^14.0.0",
44
- "express": "^4.21.2",
45
- "cors": "^2.8.5",
46
- "chokidar": "^4.0.3"
42
+ "chokidar": "^4.0.3",
43
+ "commander": "^14.0.3",
44
+ "cors": "^2.8.6",
45
+ "express": "^4.22.1"
47
46
  },
48
47
  "engines": {
49
48
  "node": ">=20.0.0"
package/scaffold.js CHANGED
@@ -1,12 +1,12 @@
1
- const fs = require('fs');
2
- const path = require('path');
1
+ const fs = require("fs");
2
+ const path = require("path");
3
3
 
4
4
  /**
5
5
  * Scaffold service for creating new addons from templates
6
6
  */
7
7
  class AddonScaffold {
8
8
  constructor() {
9
- this.templatesDir = path.join(__dirname, 'templates');
9
+ this.templatesDir = path.join(__dirname, "templates");
10
10
  }
11
11
 
12
12
  /**
@@ -14,12 +14,13 @@ class AddonScaffold {
14
14
  */
15
15
  getAvailableTemplates() {
16
16
  if (!fs.existsSync(this.templatesDir)) {
17
- throw new Error('Templates directory not found');
17
+ throw new Error("Templates directory not found");
18
18
  }
19
19
 
20
- return fs.readdirSync(this.templatesDir)
21
- .filter(file => file.endsWith('.template'))
22
- .map(file => file.replace('.template', ''));
20
+ return fs
21
+ .readdirSync(this.templatesDir)
22
+ .filter((file) => file.endsWith(".template"))
23
+ .map((file) => file.replace(".template", ""));
23
24
  }
24
25
 
25
26
  /**
@@ -27,12 +28,12 @@ class AddonScaffold {
27
28
  */
28
29
  loadTemplate(templateName) {
29
30
  const templatePath = path.join(this.templatesDir, `${templateName}.template`);
30
-
31
+
31
32
  if (!fs.existsSync(templatePath)) {
32
33
  throw new Error(`Template ${templateName} not found`);
33
34
  }
34
35
 
35
- return fs.readFileSync(templatePath, 'utf-8');
36
+ return fs.readFileSync(templatePath, "utf-8");
36
37
  }
37
38
 
38
39
  /**
@@ -41,7 +42,7 @@ class AddonScaffold {
41
42
  processTemplate(content, replacements) {
42
43
  let result = content;
43
44
  for (const [key, value] of Object.entries(replacements)) {
44
- const pattern = new RegExp(`{{${key}}}`, 'g');
45
+ const pattern = new RegExp(`{{${key}}}`, "g");
45
46
  result = result.replace(pattern, value);
46
47
  }
47
48
  return result;
@@ -51,9 +52,9 @@ class AddonScaffold {
51
52
  * Generate replacements object from addon config
52
53
  */
53
54
  generateReplacements(config) {
54
- const addonId = config.name.toLowerCase().replace(/[^a-z0-9]/g, '-');
55
+ const addonId = config.name.toLowerCase().replace(/[^a-z0-9]/g, "-");
55
56
  const packageName = `wealthfolio-${addonId}-addon`;
56
- const componentName = config.name.replace(/[^a-zA-Z0-9]/g, '');
57
+ const componentName = config.name.replace(/[^a-zA-Z0-9]/g, "");
57
58
 
58
59
  return {
59
60
  addonId,
@@ -61,7 +62,7 @@ class AddonScaffold {
61
62
  packageName,
62
63
  componentName,
63
64
  description: config.description || `A Wealthfolio addon for ${config.name}`,
64
- author: config.author || 'Anonymous'
65
+ author: config.author || "Anonymous",
65
66
  };
66
67
  }
67
68
 
@@ -70,22 +71,22 @@ class AddonScaffold {
70
71
  */
71
72
  async createAddon(config, targetDir) {
72
73
  const replacements = this.generateReplacements(config);
73
-
74
+
74
75
  // Create directory structure
75
76
  if (!fs.existsSync(targetDir)) {
76
77
  fs.mkdirSync(targetDir, { recursive: true });
77
78
  }
78
-
79
+
79
80
  // Create src directory and subdirectories
80
- const srcDir = path.join(targetDir, 'src');
81
- const srcSubDirs = ['components', 'hooks', 'pages', 'lib', 'types'];
82
-
81
+ const srcDir = path.join(targetDir, "src");
82
+ const srcSubDirs = ["components", "hooks", "pages", "lib", "types"];
83
+
83
84
  if (!fs.existsSync(srcDir)) {
84
85
  fs.mkdirSync(srcDir);
85
86
  }
86
-
87
+
87
88
  // Create all subdirectories
88
- srcSubDirs.forEach(subDir => {
89
+ srcSubDirs.forEach((subDir) => {
89
90
  const subDirPath = path.join(srcDir, subDir);
90
91
  if (!fs.existsSync(subDirPath)) {
91
92
  fs.mkdirSync(subDirPath, { recursive: true });
@@ -94,18 +95,18 @@ class AddonScaffold {
94
95
 
95
96
  // Template file mappings
96
97
  const fileTemplates = [
97
- { template: 'manifest.json', output: 'manifest.json' },
98
- { template: 'package.json', output: 'package.json' },
99
- { template: 'vite.config.ts', output: 'vite.config.ts' },
100
- { template: 'tsconfig.json', output: 'tsconfig.json' },
101
- { template: 'README.md', output: 'README.md' },
102
- { template: 'CHANGELOG.md', output: 'CHANGELOG.md' },
103
- { template: 'addon.tsx', output: 'src/addon.tsx' },
104
- { template: 'components-index.ts', output: 'src/components/index.ts' },
105
- { template: 'hooks-index.ts', output: 'src/hooks/index.ts' },
106
- { template: 'pages-index.ts', output: 'src/pages/index.ts' },
107
- { template: 'lib-index.ts', output: 'src/lib/index.ts' },
108
- { template: 'types-index.ts', output: 'src/types/index.ts' }
98
+ { template: "manifest.json", output: "manifest.json" },
99
+ { template: "package.json", output: "package.json" },
100
+ { template: "vite.config.ts", output: "vite.config.ts" },
101
+ { template: "tsconfig.json", output: "tsconfig.json" },
102
+ { template: "README.md", output: "README.md" },
103
+ { template: "CHANGELOG.md", output: "CHANGELOG.md" },
104
+ { template: "addon.tsx", output: "src/addon.tsx" },
105
+ { template: "components-index.ts", output: "src/components/index.ts" },
106
+ { template: "hooks-index.ts", output: "src/hooks/index.ts" },
107
+ { template: "pages-index.ts", output: "src/pages/index.ts" },
108
+ { template: "lib-index.ts", output: "src/lib/index.ts" },
109
+ { template: "types-index.ts", output: "src/types/index.ts" },
109
110
  ];
110
111
 
111
112
  // Process and write each template
@@ -114,13 +115,13 @@ class AddonScaffold {
114
115
  const templateContent = this.loadTemplate(template);
115
116
  const processedContent = this.processTemplate(templateContent, replacements);
116
117
  const outputPath = path.join(targetDir, output);
117
-
118
+
118
119
  // Ensure directory exists
119
120
  const outputDir = path.dirname(outputPath);
120
121
  if (!fs.existsSync(outputDir)) {
121
122
  fs.mkdirSync(outputDir, { recursive: true });
122
123
  }
123
-
124
+
124
125
  fs.writeFileSync(outputPath, processedContent);
125
126
  } catch (error) {
126
127
  throw new Error(`Failed to process template ${template}: ${error.message}`);
@@ -130,7 +131,7 @@ class AddonScaffold {
130
131
  return {
131
132
  addonDir: targetDir,
132
133
  addonId: replacements.addonId,
133
- packageName: replacements.packageName
134
+ packageName: replacements.packageName,
134
135
  };
135
136
  }
136
137
 
@@ -140,20 +141,20 @@ class AddonScaffold {
140
141
  validateConfig(config) {
141
142
  const errors = [];
142
143
 
143
- if (!config.name || typeof config.name !== 'string') {
144
- errors.push('Addon name is required and must be a string');
144
+ if (!config.name || typeof config.name !== "string") {
145
+ errors.push("Addon name is required and must be a string");
145
146
  }
146
147
 
147
148
  if (config.name && config.name.trim().length === 0) {
148
- errors.push('Addon name cannot be empty');
149
+ errors.push("Addon name cannot be empty");
149
150
  }
150
151
 
151
- if (config.description && typeof config.description !== 'string') {
152
- errors.push('Description must be a string');
152
+ if (config.description && typeof config.description !== "string") {
153
+ errors.push("Description must be a string");
153
154
  }
154
155
 
155
- if (config.author && typeof config.author !== 'string') {
156
- errors.push('Author must be a string');
156
+ if (config.author && typeof config.author !== "string") {
157
+ errors.push("Author must be a string");
157
158
  }
158
159
 
159
160
  return errors;
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to the {{name}} addon will be documented in this file.
3
+ All notable changes to the {{addonName}} addon will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
  ## [1.0.0] - {{currentDate}}
24
24
 
25
25
  ### Added
26
- - Initial release of {{name}} addon
26
+ - Initial release of {{addonName}} addon
27
27
  - Basic addon functionality and core features
28
28
  - Integration with Wealthfolio addon SDK v1.0.0
29
29
  - Sidebar navigation integration for easy access
@@ -17,19 +17,21 @@
17
17
  "type-check": "tsc --noEmit"
18
18
  },
19
19
  "dependencies": {
20
- "@wealthfolio/addon-sdk": "^1.0.0",
21
- "@wealthfolio/ui": "^1.0.0",
22
- "react": "^18.3.1",
23
- "react-dom": "^18.3.1"
20
+ "@wealthfolio/addon-sdk": "^2.0.0",
21
+ "@wealthfolio/ui": "^2.0.0",
22
+ "react": "^19.1.1",
23
+ "react-dom": "^19.1.1"
24
24
  },
25
25
  "devDependencies": {
26
- "@wealthfolio/addon-dev-tools": "^1.0.0",
27
- "@types/node": "^22.14.0",
28
- "@types/react": "^18.3.11",
29
- "@types/react-dom": "^18.3.0",
30
- "@vitejs/plugin-react": "^4.4.1",
26
+ "@tailwindcss/vite": "^4.1.13",
27
+ "@wealthfolio/addon-dev-tools": "^2.0.0",
28
+ "@types/node": "^20.0.0",
29
+ "@types/react": "^19.1.1",
30
+ "@types/react-dom": "^19.1.1",
31
+ "@vitejs/plugin-react": "^5.0.2",
31
32
  "rollup-plugin-external-globals": "^0.13.0",
32
- "typescript": "^5.8.3",
33
- "vite": "^6.2.7"
33
+ "tailwindcss": "^4.1.13",
34
+ "typescript": "^5.9.2",
35
+ "vite": "^7.1.5"
34
36
  }
35
37
  }
@@ -1,9 +1,10 @@
1
- import { defineConfig } from 'vite';
2
1
  import react from '@vitejs/plugin-react';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import { defineConfig } from 'vite';
3
4
  import externalGlobals from 'rollup-plugin-external-globals';
4
5
 
5
6
  export default defineConfig({
6
- plugins: [react()],
7
+ plugins: [react(), tailwindcss()],
7
8
  define: {
8
9
  'process.env.NODE_ENV': JSON.stringify('production'),
9
10
  },