nexfep 0.4.3 → 0.5.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/cli/help.mjs ADDED
@@ -0,0 +1,57 @@
1
+ const helpTexts = {
2
+ help: `
3
+ Nexfep - A desktop application framework for Node.js
4
+
5
+ This is a command-line tool for building desktop applications with Node.js.
6
+
7
+ For application development, please refer to the README.md:
8
+ https://github.com/nexfteam/Nexfep#readme
9
+
10
+ Usage: nexfep <command> [options]
11
+
12
+ Commands:
13
+ help Show this help message
14
+ build Build the application into a standalone executable
15
+
16
+ See 'nexfep help <command>'
17
+ to read about a specific subcommand.
18
+ `,
19
+ build: `
20
+ nexfep build - Build the application into a standalone executable
21
+
22
+ Usage:
23
+ nexfep build [options]
24
+
25
+ Options:
26
+ -n, --name <name> Application name (default: from package.json)
27
+ -e, --entry <file> Entry file path (default: from package.json main)
28
+ -o, --output <dir> Output directory (default: dist)
29
+ -i, --ignore <pattern> Files or directories to ignore (can be used multiple times)
30
+ -c, --console Show console window on Windows (default: false)
31
+ -r, --reinstall Reinstall production dependencies only before building (default: false)
32
+ -s, --skip-clean Skip cleaning old build files before building (default: false)
33
+ -u, --upx <level> Use UPX to compress the executable, level in range 0-9, 0 means no UPX compression.
34
+ Ensure UPX is installed before using (default: 0)
35
+ -m, --meta, --metadata <file> Metadata JSON file path.
36
+ For more details, see README.md and https://github.com/nexfteam/Nexfpack#Metadata.
37
+
38
+ Examples:
39
+ nexfep build # Build using defaults from package.json
40
+ nexfep build -n my-app # Set custom application name
41
+ nexfep build -e ./src/index.js # Set custom entry file
42
+ nexfep build -o ./build # Set custom output directory
43
+ nexfep build -i node_modules # Ignore node_modules
44
+ nexfep build -i test -i temp # Ignore multiple patterns
45
+
46
+ This command uses nexfpack (github.com/nexfteam/Nexfpack) to package
47
+ your application into a standalone executable. For more details, see:
48
+ https://github.com/nexfteam/Nexfpack
49
+ `,
50
+ };
51
+ export default function help(command = "help") {
52
+ if (command in helpTexts) {
53
+ console.log(helpTexts[command]);
54
+ } else {
55
+ console.log("Invalid command.");
56
+ }
57
+ }
package/cli/index.mjs CHANGED
@@ -1,20 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path from 'path';
4
- import build from './build.mjs';
5
- import help from './help.mjs';
2
+ import build from "./build.mjs";
3
+ import help from "./help.mjs";
6
4
  const args = process.argv.slice(2);
7
- if(args.length === 0) {
5
+ if (args.length === 0) {
6
+ help();
7
+ process.exit(1);
8
+ }
9
+ if (args[0] === "help") {
10
+ if (args.length > 1) {
11
+ help(args[1]);
12
+ } else {
8
13
  help();
9
- process.exit(1);
14
+ }
10
15
  }
11
- if(args[0] === 'help') {
12
- if(args.length > 1) {
13
- help(args[1]);
14
- } else {
15
- help();
16
- }
16
+ if (args[0] === "build") {
17
+ build(args.slice(1));
17
18
  }
18
- if(args[0] === 'build') {
19
- build(args.slice(1));
20
- }
@@ -0,0 +1,32 @@
1
+ export default class TimeTyper {
2
+ typerKilled;
3
+ msg;
4
+ constructor(msg) {
5
+ this.typerKilled = false;
6
+ this.msg = msg;
7
+ this.typeTime(0);
8
+ }
9
+ typeTime(seconds) {
10
+ if (this.typerKilled) {
11
+ this.typerKilled = false;
12
+ return;
13
+ }
14
+ console.log(`\x1b[1A\x1b[2K(${seconds}s) ${this.msg}`);
15
+ setTimeout(() => {
16
+ this.typeTime(seconds + 1);
17
+ }, 1000);
18
+ }
19
+ async waitForReset(resolve) {
20
+ if (!this.typerKilled) {
21
+ resolve();
22
+ } else {
23
+ setTimeout(() => {
24
+ this.waitForReset(resolve);
25
+ }, 10);
26
+ }
27
+ }
28
+ async kill() {
29
+ this.typerKilled = true;
30
+ await new Promise(this.waitForReset.bind(this));
31
+ }
32
+ }
package/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './src/Application.js';
1
+ export * from "./src/Application.js";
package/index.js CHANGED
@@ -1 +1 @@
1
- export * from './src/Application.js';
1
+ export * from "./src/Application.js";
package/package.json CHANGED
@@ -1,32 +1,24 @@
1
1
  {
2
2
  "name": "nexfep",
3
+ "version": "0.5.0",
3
4
  "description": "A desktop application framework based on @webviewjs/webview",
4
- "repository": "https://github.com/nexfteam/Nexfep",
5
- "homepage": "https://github.com/nexfteam/Nexfep#readme",
6
- "bugs": {
7
- "url": "https://github.com/nexfteam/Nexfep/issues"
8
- },
9
- "bin": {
10
- "nexfep": "cli/index.mjs"
11
- },
12
5
  "keywords": [
13
- "desktop",
6
+ "@webviewjs/webview",
14
7
  "application",
8
+ "desktop",
15
9
  "framework",
16
- "@webviewjs/webview",
17
- "webview",
18
- "typescript"
10
+ "typescript",
11
+ "webview"
19
12
  ],
20
- "version": "0.4.3",
21
- "type": "module",
22
- "main": "./index.js",
23
- "scripts": {
24
- "compile": "tsc"
13
+ "homepage": "https://github.com/nexfteam/Nexfep#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/nexfteam/Nexfep/issues"
25
16
  },
26
- "author": "nexfteam",
27
17
  "license": "MIT",
28
- "engines": {
29
- "node": ">=20.11.0"
18
+ "author": "nexfteam",
19
+ "repository": "https://github.com/nexfteam/Nexfep",
20
+ "bin": {
21
+ "nexfep": "cli/index.mjs"
30
22
  },
31
23
  "files": [
32
24
  "index.js",
@@ -35,6 +27,10 @@
35
27
  "README-CN.md",
36
28
  "LICENSE",
37
29
  "package.json",
30
+ "cli/index.mjs",
31
+ "cli/help.mjs",
32
+ "cli/build.mjs",
33
+ "cli/timeTyper.mjs",
38
34
  "src/WindowManager.js",
39
35
  "src/WindowManager.d.ts",
40
36
  "src/Application.js",
@@ -42,14 +38,31 @@
42
38
  "src/Tray.js",
43
39
  "src/Tray.d.ts",
44
40
  "src/Logger.js",
45
- "src/Logger.d.ts"
41
+ "src/Logger.d.ts",
42
+ "src/SingleInstance.js",
43
+ "src/SingleInstance.d.ts"
46
44
  ],
45
+ "type": "module",
46
+ "main": "./index.js",
47
+ "scripts": {
48
+ "compile": "tsc",
49
+ "fmt": "oxfmt --check",
50
+ "lint": "oxlint",
51
+ "fmt:fix": "oxfmt",
52
+ "lint:fix": "oxlint --fix"
53
+ },
47
54
  "dependencies": {
55
+ "@nexfteam/single-instance": "^0.0.7",
48
56
  "@webviewjs/webview": "0.4.0"
49
57
  },
50
58
  "devDependencies": {
51
59
  "@types/node": "^22.0.0",
60
+ "oxfmt": "^0.60.0",
61
+ "oxlint": "^1.75.0",
52
62
  "tsx": "^4.0.0",
53
63
  "typescript": "^5.0.0"
64
+ },
65
+ "engines": {
66
+ "node": ">=20.11.0"
54
67
  }
55
- }
68
+ }
@@ -1,7 +1,8 @@
1
- import { Application as WebviewApplication, TrayIconImage, Notification } from '@webviewjs/webview';
2
- import { WindowPool } from './WindowManager.js';
3
- import { Tray } from './Tray.js';
4
- import { Logger } from './Logger.js';
1
+ import { Application as WebviewApplication, TrayIconImage, Notification } from "@webviewjs/webview";
2
+ import { WindowPool } from "./WindowManager.js";
3
+ import { Tray } from "./Tray.js";
4
+ import { Logger } from "./Logger.js";
5
+ import { Locker } from "./SingleInstance.js";
5
6
  declare class __Utils {
6
7
  app: WebviewApplication;
7
8
  constructor(app: WebviewApplication);
@@ -25,6 +26,7 @@ declare class Application {
25
26
  label: string;
26
27
  }>;
27
28
  }): Tray;
29
+ createLocker(appName: string): Locker;
28
30
  exit(): void;
29
31
  }
30
32
  export { Application };
@@ -1,7 +1,8 @@
1
- import { Application as WebviewApplication, Notification } from '@webviewjs/webview';
2
- import { WindowPool } from './WindowManager.js';
3
- import { Tray } from './Tray.js';
4
- import { Logger } from './Logger.js';
1
+ import { Application as WebviewApplication, Notification } from "@webviewjs/webview";
2
+ import { WindowPool } from "./WindowManager.js";
3
+ import { Tray } from "./Tray.js";
4
+ import { Logger } from "./Logger.js";
5
+ import { Locker } from "./SingleInstance.js";
5
6
  class __Utils {
6
7
  app;
7
8
  constructor(app) {
@@ -21,13 +22,19 @@ class Application {
21
22
  this.app = new WebviewApplication();
22
23
  this.utils = new __Utils(this.app);
23
24
  this.logger = new Logger(options.LogFilePath);
24
- const poolOptions = { WindowsWebview2UserDataFolder: options.WindowsWebview2UserDataFolder, logger: this.logger };
25
+ const poolOptions = {
26
+ WindowsWebview2UserDataFolder: options.WindowsWebview2UserDataFolder,
27
+ logger: this.logger,
28
+ };
25
29
  this.windows = new WindowPool(this.app, poolOptions);
26
30
  this.app.whenReady();
27
31
  }
28
32
  createTray(options) {
29
33
  return new Tray(this.app, options);
30
34
  }
35
+ createLocker(appName) {
36
+ return new Locker(appName);
37
+ }
31
38
  exit() {
32
39
  this.app.exit();
33
40
  }
package/src/Logger.js CHANGED
@@ -1,4 +1,4 @@
1
- import fs from 'fs';
1
+ import fs from "fs";
2
2
  class Logger {
3
3
  logFilePath;
4
4
  constructor(logFilePath) {
@@ -6,56 +6,56 @@ class Logger {
6
6
  }
7
7
  __formatConsoleArgs(args) {
8
8
  if (!Array.isArray(args) || args.length === 0)
9
- return '';
9
+ return "";
10
10
  const template = args[0];
11
- if (typeof template !== 'string' || !template.includes('%c')) {
12
- return args.map(String).join(' ');
11
+ if (typeof template !== "string" || !template.includes("%c")) {
12
+ return args.map(String).join(" ");
13
13
  }
14
- const RESET = '\x1b[0m';
14
+ const RESET = "\x1b[0m";
15
15
  const parts = template.split(/(%c)/);
16
16
  let styleArgIndex = 1;
17
- let result = '';
18
- let currentAnsiStyle = '';
17
+ let result = "";
18
+ let currentAnsiStyle = "";
19
19
  // 命名前景色映射
20
20
  const fgColors = {
21
- black: '\x1b[30m',
22
- red: '\x1b[31m',
23
- green: '\x1b[32m',
24
- yellow: '\x1b[33m',
25
- blue: '\x1b[34m',
26
- magenta: '\x1b[35m',
27
- cyan: '\x1b[36m',
28
- white: '\x1b[37m',
29
- gray: '\x1b[90m',
30
- grey: '\x1b[90m',
31
- lightred: '\x1b[91m',
32
- lightgreen: '\x1b[92m',
33
- lightyellow: '\x1b[93m',
34
- lightblue: '\x1b[94m',
35
- lightmagenta: '\x1b[95m',
36
- lightcyan: '\x1b[96m',
37
- lightwhite: '\x1b[97m',
21
+ black: "\x1b[30m",
22
+ red: "\x1b[31m",
23
+ green: "\x1b[32m",
24
+ yellow: "\x1b[33m",
25
+ blue: "\x1b[34m",
26
+ magenta: "\x1b[35m",
27
+ cyan: "\x1b[36m",
28
+ white: "\x1b[37m",
29
+ gray: "\x1b[90m",
30
+ grey: "\x1b[90m",
31
+ lightred: "\x1b[91m",
32
+ lightgreen: "\x1b[92m",
33
+ lightyellow: "\x1b[93m",
34
+ lightblue: "\x1b[94m",
35
+ lightmagenta: "\x1b[95m",
36
+ lightcyan: "\x1b[96m",
37
+ lightwhite: "\x1b[97m",
38
38
  };
39
39
  // 命名背景色映射
40
40
  const bgColors = {
41
- black: '\x1b[40m',
42
- red: '\x1b[41m',
43
- green: '\x1b[42m',
44
- yellow: '\x1b[43m',
45
- blue: '\x1b[44m',
46
- magenta: '\x1b[45m',
47
- cyan: '\x1b[46m',
48
- white: '\x1b[47m',
41
+ black: "\x1b[40m",
42
+ red: "\x1b[41m",
43
+ green: "\x1b[42m",
44
+ yellow: "\x1b[43m",
45
+ blue: "\x1b[44m",
46
+ magenta: "\x1b[45m",
47
+ cyan: "\x1b[46m",
48
+ white: "\x1b[47m",
49
49
  };
50
50
  // 文字样式:加粗、斜体、下划线
51
51
  const textStyles = {
52
- 'bold': '\x1b[1m',
53
- 'font-weight:bold': '\x1b[1m',
54
- 'italic': '\x1b[3m',
55
- 'font-style:italic': '\x1b[3m',
56
- 'underline': '\x1b[4m',
57
- 'text-decoration:underline': '\x1b[4m',
58
- 'none': RESET,
52
+ bold: "\x1b[1m",
53
+ "font-weight:bold": "\x1b[1m",
54
+ italic: "\x1b[3m",
55
+ "font-style:italic": "\x1b[3m",
56
+ underline: "\x1b[4m",
57
+ "text-decoration:underline": "\x1b[4m",
58
+ none: RESET,
59
59
  };
60
60
  /** hex/rgb 转 ANSI 24位真彩色 */
61
61
  function colorToAnsi(colorVal, isBg = false) {
@@ -67,9 +67,12 @@ class Logger {
67
67
  // hex #fff #ffffff
68
68
  const hexReg = /^#([0-9a-f]{3}|[0-9a-f]{6})$/;
69
69
  if (hexReg.test(val)) {
70
- let hex = val.replace('#', '');
70
+ let hex = val.replace("#", "");
71
71
  if (hex.length === 3)
72
- hex = hex.split('').map(c => c + c).join('');
72
+ hex = hex
73
+ .split("")
74
+ .map((c) => c + c)
75
+ .join("");
73
76
  const r = parseInt(hex.slice(0, 2), 16);
74
77
  const g = parseInt(hex.slice(2, 4), 16);
75
78
  const b = parseInt(hex.slice(4, 6), 16);
@@ -82,30 +85,33 @@ class Logger {
82
85
  const [, r, g, b] = rgbMatch;
83
86
  return isBg ? `\x1b[48;2;${r};${g};${b}m` : `\x1b[38;2;${r};${g};${b}m`;
84
87
  }
85
- return '';
88
+ return "";
86
89
  }
87
90
  /** 解析CSS样式字符串为ANSI序列 */
88
91
  function parseColorStyle(styleStr) {
89
92
  if (!styleStr)
90
93
  return RESET;
91
- let ansi = '';
92
- const rules = styleStr.split(';').map(s => s.trim()).filter(Boolean);
94
+ let ansi = "";
95
+ const rules = styleStr
96
+ .split(";")
97
+ .map((s) => s.trim())
98
+ .filter(Boolean);
93
99
  for (const rule of rules) {
94
- const [prop, ...rest] = rule.split(':');
100
+ const [prop, ...rest] = rule.split(":");
95
101
  const propKey = prop.trim().toLowerCase();
96
- const value = rest.join(':').trim().toLowerCase();
102
+ const value = rest.join(":").trim().toLowerCase();
97
103
  // 文字样式
98
104
  if (textStyles[`${propKey}:${value}`] || textStyles[value]) {
99
105
  ansi += textStyles[`${propKey}:${value}`] ?? textStyles[value];
100
106
  continue;
101
107
  }
102
108
  // 前景色
103
- if (propKey === 'color') {
109
+ if (propKey === "color") {
104
110
  ansi += colorToAnsi(value, false);
105
111
  continue;
106
112
  }
107
113
  // 背景色
108
- if (propKey === 'background' || propKey === 'background-color') {
114
+ if (propKey === "background" || propKey === "background-color") {
109
115
  ansi += colorToAnsi(value, true);
110
116
  continue;
111
117
  }
@@ -113,14 +119,14 @@ class Logger {
113
119
  return ansi;
114
120
  }
115
121
  for (const segment of parts) {
116
- if (segment === '%c') {
122
+ if (segment === "%c") {
117
123
  // 取出下一个样式参数,参数不足则重置样式
118
124
  const styleStr = args[styleArgIndex];
119
125
  styleArgIndex++;
120
- currentAnsiStyle = parseColorStyle(String(styleStr ?? ''));
126
+ currentAnsiStyle = parseColorStyle(String(styleStr ?? ""));
121
127
  }
122
128
  else {
123
- if (segment === '')
129
+ if (segment === "")
124
130
  continue;
125
131
  if (currentAnsiStyle) {
126
132
  result += currentAnsiStyle + segment;
@@ -136,54 +142,54 @@ class Logger {
136
142
  // 剩余非样式参数直接拼接
137
143
  const extraArgs = args.slice(styleArgIndex);
138
144
  if (extraArgs.length) {
139
- result += ' ' + extraArgs.map(String).join(' ');
145
+ result += " " + extraArgs.map(String).join(" ");
140
146
  }
141
147
  return result;
142
148
  }
143
149
  __printLog(winid, type, args, colorANSI, colorRESET) {
144
150
  if (this.logFilePath) {
145
- fs.appendFileSync(this.logFilePath, `${new Date().toISOString()} [${winid}] ${type}: ${args.join(' ')}\n`);
151
+ fs.appendFileSync(this.logFilePath, `${new Date().toISOString()} [${winid}] ${type}: ${args.join(" ")}\n`);
146
152
  }
147
- console.log(`${colorANSI || ''}[${winid}] ${type}:${colorRESET || ''}`, this.__formatConsoleArgs(args));
153
+ console.log(`${colorANSI || ""}[${winid}] ${type}:${colorRESET || ""}`, this.__formatConsoleArgs(args));
148
154
  }
149
155
  log(args) {
150
156
  if (Array.isArray(args)) {
151
- this.__printLog(0, 'Log', args);
157
+ this.__printLog(0, "Log", args);
152
158
  }
153
159
  else {
154
- this.__printLog(0, 'Log', [args]);
160
+ this.__printLog(0, "Log", [args]);
155
161
  }
156
162
  }
157
163
  error(args) {
158
164
  if (Array.isArray(args)) {
159
- this.__printLog(0, 'Error', args, '\x1b[31m', '\x1b[0m');
165
+ this.__printLog(0, "Error", args, "\x1b[31m", "\x1b[0m");
160
166
  }
161
167
  else {
162
- this.__printLog(0, 'Error', [args], '\x1b[31m', '\x1b[0m');
168
+ this.__printLog(0, "Error", [args], "\x1b[31m", "\x1b[0m");
163
169
  }
164
170
  }
165
171
  warn(args) {
166
172
  if (Array.isArray(args)) {
167
- this.__printLog(0, 'Warn', args, '\x1b[33m', '\x1b[0m');
173
+ this.__printLog(0, "Warn", args, "\x1b[33m", "\x1b[0m");
168
174
  }
169
175
  else {
170
- this.__printLog(0, 'Warn', [args], '\x1b[33m', '\x1b[0m');
176
+ this.__printLog(0, "Warn", [args], "\x1b[33m", "\x1b[0m");
171
177
  }
172
178
  }
173
179
  info(args) {
174
180
  if (Array.isArray(args)) {
175
- this.__printLog(0, 'Info', args, '\x1b[34m', '\x1b[0m');
181
+ this.__printLog(0, "Info", args, "\x1b[34m", "\x1b[0m");
176
182
  }
177
183
  else {
178
- this.__printLog(0, 'Info', [args], '\x1b[34m', '\x1b[0m');
184
+ this.__printLog(0, "Info", [args], "\x1b[34m", "\x1b[0m");
179
185
  }
180
186
  }
181
187
  debug(args) {
182
188
  if (Array.isArray(args)) {
183
- this.__printLog(0, 'Debug', args, '\x1b[90m', '\x1b[0m');
189
+ this.__printLog(0, "Debug", args, "\x1b[90m", "\x1b[0m");
184
190
  }
185
191
  else {
186
- this.__printLog(0, 'Debug', [args], '\x1b[90m', '\x1b[0m');
192
+ this.__printLog(0, "Debug", [args], "\x1b[90m", "\x1b[0m");
187
193
  }
188
194
  }
189
195
  }
@@ -0,0 +1,9 @@
1
+ import { SingleInstance } from "@nexfteam/single-instance";
2
+ declare class Locker {
3
+ locker: SingleInstance;
4
+ constructor(appName: string);
5
+ lock(): Promise<void>;
6
+ unlock(): Promise<void>;
7
+ whenLost(callback: () => void): void;
8
+ }
9
+ export { Locker };
@@ -0,0 +1,17 @@
1
+ import { SingleInstance } from "@nexfteam/single-instance";
2
+ class Locker {
3
+ locker;
4
+ constructor(appName) {
5
+ this.locker = new SingleInstance(appName);
6
+ }
7
+ lock() {
8
+ return this.locker.lock();
9
+ }
10
+ unlock() {
11
+ return this.locker.unlock();
12
+ }
13
+ whenLost(callback) {
14
+ this.locker.on("connection-attempt", callback);
15
+ }
16
+ }
17
+ export { Locker };
package/src/Tray.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Application, TrayIcon, TrayIconImage } from '@webviewjs/webview';
1
+ import { Application, TrayIcon, TrayIconImage } from "@webviewjs/webview";
2
2
  declare class Tray {
3
3
  app: Application;
4
4
  tray: TrayIcon;
package/src/Tray.js CHANGED
@@ -12,13 +12,13 @@ class Tray {
12
12
  tooltip: options.tooltip,
13
13
  icon: options.icon,
14
14
  menu: {
15
- items: this.menuItems
16
- }
15
+ items: this.menuItems,
16
+ },
17
17
  });
18
18
  }
19
19
  __ErrorWhenDestroyed() {
20
20
  if (this.destroyed) {
21
- throw new Error('Tray has been destroyed');
21
+ throw new Error("Tray has been destroyed");
22
22
  }
23
23
  }
24
24
  addMenuItem(item) {
@@ -28,7 +28,7 @@ class Tray {
28
28
  }
29
29
  removeMenuItem(id) {
30
30
  this.__ErrorWhenDestroyed();
31
- this.menuItems = this.menuItems.filter(item => item.id !== id);
31
+ this.menuItems = this.menuItems.filter((item) => item.id !== id);
32
32
  this.tray.setMenu({ items: this.menuItems });
33
33
  }
34
34
  setMenuItems(items) {
@@ -16,9 +16,18 @@ declare class Window {
16
16
  minimize(): void;
17
17
  unMaximize(): void;
18
18
  unMinimize(): void;
19
+ isMaximized(): boolean;
20
+ isMinimized(): boolean;
21
+ toggleMaximize(): void;
22
+ toggleMinimize(): void;
19
23
  openDevTools(): void;
20
24
  closeDevTools(): void;
21
25
  setSize(width: number, height: number): void;
26
+ getSize(): import("@webviewjs/webview").Dimensions;
27
+ setPosition(x: number, y: number): void;
28
+ getPosition(): import("@webviewjs/webview").Position;
29
+ isFocused(): boolean;
30
+ focus(): void;
22
31
  hide(): void;
23
32
  show(): void;
24
33
  close(): void;