appclean 2.0.0 → 2.0.2

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 (43) hide show
  1. package/.github/workflows/npm-publish.yml +61 -0
  2. package/DEVELOPMENT.md +84 -0
  3. package/RELEASE_GUIDE.md +236 -0
  4. package/dist/index.js +1 -1
  5. package/dist/ui/client/api/client.d.ts.map +1 -1
  6. package/dist/ui/client/api/client.js +5 -1
  7. package/dist/ui/client/api/client.js.map +1 -1
  8. package/dist/ui/client/app.d.ts +1 -1
  9. package/dist/ui/client/app.d.ts.map +1 -1
  10. package/dist/ui/client/app.js +10 -6
  11. package/dist/ui/client/app.js.map +1 -1
  12. package/dist/ui/client/pages/appSearch.js +12 -1
  13. package/dist/ui/client/pages/appSearch.js.map +1 -1
  14. package/dist/ui/client/pages/dashboard.d.ts.map +1 -1
  15. package/dist/ui/client/pages/dashboard.js +26 -5
  16. package/dist/ui/client/pages/dashboard.js.map +1 -1
  17. package/dist/ui/client/state/appStore.d.ts.map +1 -1
  18. package/dist/ui/client/state/appStore.js +21 -12
  19. package/dist/ui/client/state/appStore.js.map +1 -1
  20. package/dist/ui/client/state/dashboardStore.d.ts.map +1 -1
  21. package/dist/ui/client/state/dashboardStore.js +9 -3
  22. package/dist/ui/client/state/dashboardStore.js.map +1 -1
  23. package/dist/ui/client/styles/animations.css +22 -0
  24. package/dist/ui/guiServer.d.ts +3 -0
  25. package/dist/ui/guiServer.d.ts.map +1 -1
  26. package/dist/ui/guiServer.js +48 -1
  27. package/dist/ui/guiServer.js.map +1 -1
  28. package/dist/utils/upgrade.d.ts +2 -1
  29. package/dist/utils/upgrade.d.ts.map +1 -1
  30. package/dist/utils/upgrade.js +14 -1
  31. package/dist/utils/upgrade.js.map +1 -1
  32. package/package.json +1 -1
  33. package/scripts/publish-npm.sh +64 -0
  34. package/src/index.ts +1 -1
  35. package/src/ui/client/api/client.ts +6 -1
  36. package/src/ui/client/app.ts +15 -11
  37. package/src/ui/client/pages/appSearch.ts +14 -1
  38. package/src/ui/client/pages/dashboard.ts +27 -5
  39. package/src/ui/client/state/appStore.ts +24 -12
  40. package/src/ui/client/state/dashboardStore.ts +13 -3
  41. package/src/ui/client/styles/animations.css +22 -0
  42. package/src/ui/guiServer.ts +67 -1
  43. package/src/utils/upgrade.ts +18 -1
@@ -57,16 +57,20 @@ export class AppStore extends Store<AppStoreState> {
57
57
  const response = await fetch('/api/apps/list');
58
58
  if (!response.ok) throw new Error('Failed to load apps');
59
59
 
60
- const data = await response.json();
60
+ const json = await response.json();
61
+ const data = json.data || json; // Handle wrapped { success, data } format
62
+
61
63
  this.setState({
62
- apps: data.apps,
63
- total: data.total,
64
- page: data.page,
64
+ apps: data.apps || [],
65
+ total: data.total || 0,
66
+ page: data.page || 1,
65
67
  isLoading: false,
66
68
  });
67
69
  } catch (error) {
70
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
71
+ console.error('Failed to load apps:', errorMsg);
68
72
  this.setState({
69
- error: (error as Error).message,
73
+ error: errorMsg,
70
74
  isLoading: false,
71
75
  });
72
76
  }
@@ -88,15 +92,19 @@ export class AppStore extends Store<AppStoreState> {
88
92
  const response = await fetch(`/api/apps/search?${params}`);
89
93
  if (!response.ok) throw new Error('Failed to search apps');
90
94
 
91
- const data = await response.json();
95
+ const json = await response.json();
96
+ const data = json.data || json; // Handle wrapped { success, data } format
97
+
92
98
  this.setState({
93
- apps: data.apps,
94
- total: data.count,
99
+ apps: data.apps || [],
100
+ total: data.count || 0,
95
101
  isLoading: false,
96
102
  });
97
103
  } catch (error) {
104
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
105
+ console.error('Failed to search apps:', errorMsg);
98
106
  this.setState({
99
- error: (error as Error).message,
107
+ error: errorMsg,
100
108
  isLoading: false,
101
109
  });
102
110
  }
@@ -145,15 +153,19 @@ export class AppStore extends Store<AppStoreState> {
145
153
  const response = await fetch(`/api/apps/search?${params}`);
146
154
  if (!response.ok) throw new Error('Failed to load more apps');
147
155
 
148
- const data = await response.json();
156
+ const json = await response.json();
157
+ const data = json.data || json; // Handle wrapped { success, data } format
158
+
149
159
  this.setState({
150
- apps: [...this.state.apps, ...data.apps],
160
+ apps: [...this.state.apps, ...(data.apps || [])],
151
161
  page: nextPage,
152
162
  isLoading: false,
153
163
  });
154
164
  } catch (error) {
165
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
166
+ console.error('Failed to load next page:', errorMsg);
155
167
  this.setState({
156
- error: (error as Error).message,
168
+ error: errorMsg,
157
169
  isLoading: false,
158
170
  });
159
171
  }
@@ -44,17 +44,27 @@ export class DashboardStore extends Store<DashboardStoreState> {
44
44
  this.setState({ isLoading: true, error: null });
45
45
  try {
46
46
  const response = await fetch('/api/dashboard/stats');
47
- if (!response.ok) throw new Error('Failed to load dashboard stats');
47
+ if (!response.ok) throw new Error(`Failed to load dashboard stats: ${response.status}`);
48
+
49
+ const data = await response.json();
50
+
51
+ // Extract stats from API response format { success: true, data: {...} }
52
+ const stats = data.data || data;
53
+
54
+ if (!stats) {
55
+ throw new Error('Invalid response format from server');
56
+ }
48
57
 
49
- const stats = await response.json();
50
58
  this.setState({
51
59
  stats,
52
60
  isLoading: false,
53
61
  lastUpdated: Date.now(),
54
62
  });
55
63
  } catch (error) {
64
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
65
+ console.error('Failed to load dashboard stats:', errorMsg);
56
66
  this.setState({
57
- error: (error as Error).message,
67
+ error: errorMsg,
58
68
  isLoading: false,
59
69
  });
60
70
  }
@@ -39,6 +39,28 @@
39
39
  }
40
40
  }
41
41
 
42
+ @keyframes slideUp {
43
+ from {
44
+ opacity: 0;
45
+ transform: translateY(20px);
46
+ }
47
+ to {
48
+ opacity: 1;
49
+ transform: translateY(0);
50
+ }
51
+ }
52
+
53
+ @keyframes slideIn {
54
+ from {
55
+ opacity: 0;
56
+ transform: translateX(-10px);
57
+ }
58
+ to {
59
+ opacity: 1;
60
+ transform: translateX(0);
61
+ }
62
+ }
63
+
42
64
  @keyframes slideOutDown {
43
65
  from {
44
66
  opacity: 1;
@@ -8,6 +8,7 @@ import { readFileSync, existsSync } from 'fs';
8
8
  import { join } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { dirname } from 'path';
11
+ import { execFile, spawn, type ChildProcess } from 'child_process';
11
12
  import { Logger } from '../utils/logger.js';
12
13
  import { sendJson, sendError, parseQueryParams } from './server/middleware/errorHandler.js';
13
14
  import { handleAppRoutes } from './server/routes/apps.js';
@@ -22,6 +23,7 @@ export class GUIServer {
22
23
  private port: number = 3000;
23
24
  private server: any;
24
25
  private spaHtml: string | null = null;
26
+ private browserProcess: ChildProcess | null = null;
25
27
 
26
28
  constructor(port: number = 3000) {
27
29
  this.port = port;
@@ -41,9 +43,13 @@ export class GUIServer {
41
43
  });
42
44
 
43
45
  return new Promise((resolve) => {
44
- this.server.listen(this.port, () => {
46
+ this.server.listen(this.port, async () => {
45
47
  Logger.success(`✨ AppClean GUI running at http://localhost:${this.port}`);
46
48
  Logger.info('Press Ctrl+C to stop the server');
49
+
50
+ // Open the default browser
51
+ await this.openBrowser();
52
+
47
53
  resolve();
48
54
  });
49
55
  });
@@ -53,12 +59,72 @@ export class GUIServer {
53
59
  * Stop GUI server
54
60
  */
55
61
  async stop(): Promise<void> {
62
+ // Close the browser
63
+ await this.closeBrowser();
64
+
56
65
  if (this.server) {
57
66
  this.server.close();
58
67
  Logger.info('GUI server stopped');
59
68
  }
60
69
  }
61
70
 
71
+ /**
72
+ * Open the default browser
73
+ */
74
+ private async openBrowser(): Promise<void> {
75
+ const url = `http://localhost:${this.port}`;
76
+
77
+ try {
78
+ const platform = process.platform;
79
+
80
+ if (platform === 'darwin') {
81
+ // macOS
82
+ this.browserProcess = execFile('open', [url]);
83
+ } else if (platform === 'win32') {
84
+ // Windows
85
+ this.browserProcess = execFile('cmd', ['/c', 'start', url]);
86
+ } else {
87
+ // Linux and other Unix-like systems
88
+ this.browserProcess = execFile('xdg-open', [url]);
89
+ }
90
+
91
+ this.browserProcess.on('error', (error) => {
92
+ Logger.warn(`Could not open browser: ${error.message}`);
93
+ });
94
+ } catch (error) {
95
+ Logger.warn(`Failed to open browser: ${(error as Error).message}`);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Close the browser
101
+ */
102
+ private async closeBrowser(): Promise<void> {
103
+ if (!this.browserProcess) {
104
+ return;
105
+ }
106
+
107
+ try {
108
+ const platform = process.platform;
109
+
110
+ if (platform === 'win32') {
111
+ // Windows: kill the process
112
+ if (this.browserProcess.pid) {
113
+ process.kill(this.browserProcess.pid, 'SIGTERM');
114
+ }
115
+ } else {
116
+ // macOS and Linux: kill the process
117
+ if (this.browserProcess.pid) {
118
+ process.kill(this.browserProcess.pid);
119
+ }
120
+ }
121
+
122
+ this.browserProcess = null;
123
+ } catch (error) {
124
+ Logger.debug(`Note: Could not close browser process: ${(error as Error).message}`);
125
+ }
126
+ }
127
+
62
128
  /**
63
129
  * Main request handler
64
130
  */
@@ -1,5 +1,8 @@
1
1
  import { execSync } from 'child_process';
2
2
  import { Logger } from './logger.js';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
3
6
 
4
7
  export interface VersionInfo {
5
8
  current: string;
@@ -9,7 +12,21 @@ export interface VersionInfo {
9
12
 
10
13
  export class UpgradeManager {
11
14
  private readonly packageName = 'appclean';
12
- private readonly currentVersion = '1.9.0';
15
+ private currentVersion: string;
16
+
17
+ constructor() {
18
+ // Dynamically read version from package.json
19
+ try {
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+ const packagePath = join(__dirname, '../../package.json');
23
+ const packageData = JSON.parse(readFileSync(packagePath, 'utf-8'));
24
+ this.currentVersion = packageData.version || '2.0.0';
25
+ } catch (error) {
26
+ Logger.debug('Failed to read version from package.json, using fallback');
27
+ this.currentVersion = '2.0.0';
28
+ }
29
+ }
13
30
 
14
31
  /**
15
32
  * Get version information from npm registry