quapp 1.1.2 → 1.1.3

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
@@ -1,193 +1,127 @@
1
1
  # quapp
2
2
 
3
- Development CLI for Quapp projects - start a dev server with LAN QR code and build `.qpp` packages.
3
+ Dev server and build tool for Quapp projects. Serves your app over LAN with a QR code for mobile testing, and packages production builds as `.qpp` files.
4
4
 
5
- ## Installation
5
+ ## Quick Start
6
+
7
+ If you scaffolded with `create-quapp`, run the pre-configured scripts:
6
8
 
7
9
  ```bash
8
- npm install -D quapp
10
+ npm run dev # Start dev server
11
+ npm run qbuild # Build .qpp package
9
12
  ```
10
13
 
11
- ## Commands
14
+ ## Installation
12
15
 
13
- ### `quapp init`
16
+ For existing Vite projects:
14
17
 
15
- Initialize Quapp in an existing project. Creates config and adds scripts.
18
+ ```bash
19
+ npm install -D quapp
20
+ ```
21
+
22
+ Initialize configuration and scripts:
16
23
 
17
24
  ```bash
18
- quapp init [options]
25
+ npx quapp init
19
26
  ```
20
27
 
21
- **Options:**
22
- | Flag | Short | Description |
23
- |------|-------|-------------|
24
- | `--yes` | `-y` | Skip confirmation prompt |
25
- | `--force` | `-f` | Overwrite existing config/scripts |
26
- | `--dry-run` | | Preview changes without applying |
28
+ ## Commands
27
29
 
28
30
  ### `quapp serve`
29
31
 
30
- Start development server with LAN access and QR code for mobile testing.
32
+ Starts Vite dev server with LAN network access and QR code.
31
33
 
32
34
  ```bash
33
- quapp serve [options]
35
+ npx quapp serve
34
36
  ```
35
37
 
36
- **Options:**
37
- | Flag | Short | Description |
38
- |------|-------|-------------|
39
- | `--port <port>` | `-p` | Port to run on (default: 5173) |
40
- | `--host <host>` | | Host to bind to |
41
- | `--open` | | Open browser automatically |
42
- | `--no-qr` | | Disable QR code display |
43
- | `--https` | | Enable HTTPS |
38
+ | Flag | Description |
39
+ |------|-------------|
40
+ | `-p, --port <port>` | Server port (default: 5173) |
41
+ | `--host <host>` | Host to bind |
42
+ | `--open` | Open in browser |
43
+ | `--no-qr` | Disable QR code |
44
+ | `--https` | Enable HTTPS |
44
45
 
45
46
  ### `quapp build`
46
47
 
47
- Build for production and create `.qpp` package.
48
+ Builds for production and creates `.qpp` package.
48
49
 
49
50
  ```bash
50
- quapp build [options]
51
+ npx quapp build
51
52
  ```
52
53
 
53
- **Options:**
54
- | Flag | Short | Description |
55
- |------|-------|-------------|
56
- | `--output <file>` | `-o` | Output filename (default: dist.qpp) |
57
- | `--no-clean` | | Keep dist folder after build |
58
- | `--skip-prompts` | | Skip interactive prompts |
54
+ | Flag | Description |
55
+ |------|-------------|
56
+ | `-o, --output <file>` | Output filename (default: dist.qpp) |
57
+ | `--skip-prompts` | Non-interactive mode |
58
+ | `--no-clean` | Keep dist folder |
59
59
 
60
- ## Global Options
60
+ ### `quapp init`
61
+
62
+ Initialize Quapp in an existing Vite project.
63
+
64
+ ```bash
65
+ npx quapp init
66
+ ```
61
67
 
62
- | Flag | Short | Description |
63
- |------|-------|-------------|
64
- | `--json` | | Output as JSON (for automation/AI) |
65
- | `--no-color` | | Disable colored output |
66
- | `--verbose` | | Show detailed logs |
67
- | `--version` | `-v` | Show version |
68
- | `--help` | `-h` | Show help |
68
+ | Flag | Description |
69
+ |------|-------------|
70
+ | `-y, --yes` | Skip prompts |
71
+ | `-f, --force` | Overwrite existing config |
72
+ | `--dry-run` | Preview changes |
69
73
 
70
74
  ## Configuration
71
75
 
72
- Create `quapp.config.json` in your project root:
76
+ `quapp.config.json`:
73
77
 
74
78
  ```json
75
79
  {
76
80
  "server": {
77
81
  "port": 5173,
78
82
  "qr": true,
79
- "network": "private",
80
- "openBrowser": false,
81
- "https": false,
82
- "fallbackPort": true,
83
- "autoRetry": true,
84
- "strictPort": false
83
+ "openBrowser": false
85
84
  },
86
85
  "build": {
87
- "outDir": "dist",
88
86
  "outputFile": "dist.qpp"
89
87
  }
90
88
  }
91
89
  ```
92
90
 
93
- ## Examples
94
-
95
- ```bash
96
- # Initialize Quapp in existing project
97
- quapp init
98
-
99
- # Initialize without prompts (AI-friendly)
100
- quapp init --yes --json
101
-
102
- # Start dev server
103
- quapp serve
104
-
105
- # Start on specific port
106
- quapp serve -p 3000
107
-
108
- # Start and open browser
109
- quapp serve --open
110
-
111
- # Build for production
112
- quapp build
113
-
114
- # Build with custom output name
115
- quapp build -o my-app.qpp
91
+ ## Global Options
116
92
 
117
- # Build with JSON output (AI-friendly)
118
- quapp build --json --skip-prompts
119
- ```
93
+ | Flag | Description |
94
+ |------|-------------|
95
+ | `--json` | JSON output for automation |
96
+ | `--verbose` | Detailed logging |
97
+ | `-h, --help` | Show help |
98
+ | `-v, --version` | Show version |
120
99
 
121
- ## AI/Automation Usage
100
+ ## Automation
122
101
 
123
- For non-interactive environments:
102
+ For CI/CD or programmatic usage:
124
103
 
125
104
  ```bash
126
- # Initialize without prompts
127
- quapp init --yes --json
128
-
129
- # Preview init changes
130
- quapp init --dry-run --json
131
-
132
- # Build without prompts
133
- quapp build --skip-prompts --json
134
-
135
- # Build with custom output
136
- quapp build -o myapp.qpp --skip-prompts --json
105
+ npx quapp build --skip-prompts --json
137
106
  ```
138
107
 
139
- ### JSON Output Examples
140
-
141
- Init success:
142
- ```json
143
- {
144
- "success": true,
145
- "changes": ["quapp.config.json", "script:dev", "script:qbuild", "devDependency:quapp"],
146
- "nextSteps": ["npm install", "npm run dev"]
147
- }
148
- ```
149
-
150
- Build success:
151
108
  ```json
152
109
  {
153
110
  "success": true,
154
111
  "outputFile": "dist.qpp",
155
- "outputPath": "/path/to/project/dist.qpp",
156
- "manifest": {
157
- "package_name": "com.author.myapp",
158
- "version": "1.0.0",
159
- "version_code": 10000
160
- },
161
- "duration": 5230
162
- }
163
- ```
164
-
165
- Error (with suggestion for AI):
166
- ```json
167
- {
168
- "success": false,
169
- "errorCode": "NO_BUILD_SCRIPT",
170
- "error": "No build script",
171
- "suggestion": "Add to package.json: \"scripts\": { \"build\": \"vite build\" }"
112
+ "outputPath": "/path/to/dist.qpp",
113
+ "duration": 1234
172
114
  }
173
115
  ```
174
116
 
175
- ## Exit Codes
117
+ ## Requirements
176
118
 
177
- | Code | Description |
178
- |------|-------------|
179
- | 0 | Success |
180
- | 1 | General error |
181
- | 2 | Invalid arguments |
182
- | 3 | Build failed |
183
- | 4 | Configuration error |
184
- | 5 | Missing dependency |
185
- | 130 | User cancelled |
119
+ - Node.js 18+
120
+ - Vite project
186
121
 
187
- ## Requirements
122
+ ## Related
188
123
 
189
- - Node.js >= 18.0.0
190
- - Vite (in your project's dependencies)
124
+ - [create-quapp](https://www.npmjs.com/package/create-quapp) Project scaffolding
191
125
 
192
126
  ## License
193
127
 
package/bin/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * quapp CLI - Development and build tools for Quapp projects
package/commands/serve.js CHANGED
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Serve command - Start development server with LAN access
3
+ *
4
+ * Design: We use --strictPort so Vite exits on port conflicts, allowing us to:
5
+ * 1. Control ALL user-facing messages (no duplicate/conflicting output)
6
+ * 2. Show accurate port information
7
+ * 3. Provide a clean, quapp-branded DX
3
8
  */
4
9
 
5
10
  import { spawn } from 'child_process';
6
11
  import os from 'os';
12
+ import net from 'net';
7
13
  import * as logger from '../lib/logger.js';
8
14
  import { loadConfig, checkViteAvailable } from '../lib/config.js';
9
15
  import { EXIT_CODES } from '../lib/constants.js';
@@ -27,6 +33,50 @@ function getIP(networkType = 'private') {
27
33
  return 'localhost';
28
34
  }
29
35
 
36
+ /**
37
+ * Check if a port is available
38
+ * @param {number} port - Port to check
39
+ * @param {string} host - Host to check on
40
+ * @returns {Promise<boolean>}
41
+ */
42
+ function isPortAvailable(port, host = 'localhost') {
43
+ return new Promise((resolve) => {
44
+ const server = net.createServer();
45
+ server.once('error', () => {
46
+ server.close(); // Clean up even on error to prevent fd leak
47
+ resolve(false);
48
+ });
49
+ server.once('listening', () => {
50
+ server.close();
51
+ resolve(true);
52
+ });
53
+ server.listen(port, host);
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Find an available port starting from the given port
59
+ * @param {number} startPort - Port to start searching from
60
+ * @param {string} host - Host to check on
61
+ * @param {number} maxAttempts - Maximum number of ports to try
62
+ * @returns {Promise<{port: number, retries: number}>}
63
+ */
64
+ async function findAvailablePort(startPort, host = 'localhost', maxAttempts = 10) {
65
+ let port = startPort;
66
+ let retries = 0;
67
+
68
+ while (retries < maxAttempts) {
69
+ if (await isPortAvailable(port, host)) {
70
+ return { port, retries };
71
+ }
72
+ logger.info(`Port ${port} is in use, trying ${port + 1}...`);
73
+ port++;
74
+ retries++;
75
+ }
76
+
77
+ throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
78
+ }
79
+
30
80
  /**
31
81
  * Run the serve command
32
82
  * @param {Object} options - Command options
@@ -61,19 +111,29 @@ export async function runServe(options = {}) {
61
111
  return { success: false, error: 'Vite not found', exitCode: EXIT_CODES.MISSING_DEPENDENCY };
62
112
  }
63
113
 
64
- logger.debug(`Starting Vite on ${serverConfig.host}:${serverConfig.port}`);
114
+ // Find available port BEFORE starting Vite (if autoRetry is enabled)
115
+ let actualPort = serverConfig.port;
116
+
117
+ if (config.server.autoRetry && !serverConfig.strictPort) {
118
+ try {
119
+ const { port, retries } = await findAvailablePort(serverConfig.port, serverConfig.host);
120
+ actualPort = port;
121
+ } catch (err) {
122
+ logger.error(err.message);
123
+ return { success: false, error: err.message, exitCode: EXIT_CODES.GENERAL_ERROR };
124
+ }
125
+ }
65
126
 
66
- // Build Vite arguments
127
+ logger.debug(`Starting dev server on ${serverConfig.host}:${actualPort}`);
128
+
129
+ // Build Vite arguments - always use strictPort since we handle port finding ourselves
67
130
  const viteArgs = [
68
131
  'vite',
69
132
  '--host', serverConfig.host,
70
- '--port', String(serverConfig.port),
133
+ '--port', String(actualPort),
134
+ '--strictPort', // Always strict since we pre-check port availability
71
135
  ];
72
136
 
73
- if (serverConfig.strictPort || !config.server.autoRetry) {
74
- viteArgs.push('--strictPort');
75
- }
76
-
77
137
  if (serverConfig.https) {
78
138
  viteArgs.push('--https');
79
139
  }
@@ -90,24 +150,22 @@ export async function runServe(options = {}) {
90
150
  shell: true,
91
151
  });
92
152
 
93
- let qrShown = false;
94
-
95
- // Build URLs - LAN URL for QR code, localhost for browser
153
+ let serverReady = false;
154
+ let bannerShown = false;
96
155
  const protocol = serverConfig.https ? 'https' : 'http';
97
- const lanUrl = `${protocol}://${serverConfig.host}:${serverConfig.port}`;
98
- const localUrl = `${protocol}://localhost:${serverConfig.port}`;
156
+ const lanUrl = `${protocol}://${serverConfig.host}:${actualPort}`;
157
+ const localUrl = `${protocol}://localhost:${actualPort}`;
158
+
159
+ // Function to show our custom banner with QR code
160
+ const showBanner = async () => {
161
+ if (bannerShown) return;
162
+ bannerShown = true;
99
163
 
100
- // Function to show QR code and open browser
101
- const showQRAndOpenBrowser = async () => {
102
- if (qrShown) return;
103
- qrShown = true;
104
-
105
- // Show access URLs
106
164
  logger.newline();
107
- console.log(' \x1b[1m\x1b[32m✓\x1b[0m \x1b[1mServer running!\x1b[0m');
165
+ console.log(' \x1b[1m\x1b[32m✓\x1b[0m \x1b[1mDev server running!\x1b[0m');
108
166
  logger.newline();
109
167
  // console.log(` \x1b[36m➜\x1b[0m \x1b[1mLocal:\x1b[0m ${localUrl}`);
110
- console.log(` \x1b[36m➜\x1b[0m \x1b[1mNetwork:\x1b[0m ${lanUrl}`);
168
+ // console.log(` \x1b[36m➜\x1b[0m \x1b[1mNetwork:\x1b[0m ${lanUrl}`);
111
169
 
112
170
  // Show QR code for mobile access
113
171
  if (serverConfig.qr) {
@@ -118,33 +176,55 @@ export async function runServe(options = {}) {
118
176
  logger.newline();
119
177
  qrcode.default.generate(lanUrl, { small: true });
120
178
  } catch (err) {
121
- // qrcode-terminal not available, skip
122
179
  logger.debug(`QR code generation failed: ${err.message}`);
123
180
  }
124
181
  }
125
182
 
183
+ logger.newline();
184
+ console.log(' \x1b[90mpress h + enter to show help\x1b[0m');
126
185
  logger.newline();
127
186
 
128
- // Open browser if requested - use localhost for local browser access
187
+ // Open browser if requested
129
188
  if (serverConfig.openBrowser) {
130
189
  try {
131
190
  const open = await import('open');
132
- await open.default(lanUrl);
191
+ await open.default(localUrl);
133
192
  } catch (err) {
134
193
  logger.debug(`Failed to open browser: ${err.message}`);
135
194
  }
136
195
  }
137
196
  };
138
197
 
139
- // Show QR code after Vite has had time to start (simple and reliable)
140
- setTimeout(showQRAndOpenBrowser, 1500);
141
-
142
- // Handle stdout - just pass through to console
198
+ // Handle stdout - filter Vite's startup banner, pass through HMR/other messages
143
199
  viteProcess.stdout.on('data', (data) => {
200
+ const output = data.toString();
201
+
202
+ // Detect when Vite is ready
203
+ if (output.includes('ready in')) {
204
+ serverReady = true;
205
+ // Show our banner instead of Vite's
206
+ showBanner();
207
+ return; // Don't print Vite's ready line
208
+ }
209
+
210
+ // Filter out Vite's startup banner lines (we show our own)
211
+ // These patterns match Vite's default startup output
212
+ const isStartupBanner =
213
+ output.includes('VITE v') ||
214
+ output.includes('➜ Local:') ||
215
+ output.includes('➜ Network:') ||
216
+ output.includes('press h + enter') ||
217
+ (output.trim() === '' && !serverReady); // Empty lines before ready
218
+
219
+ if (isStartupBanner) {
220
+ return; // Suppress Vite's banner
221
+ }
222
+
223
+ // Pass through all other output (HMR updates, warnings, etc.)
144
224
  process.stdout.write(data);
145
225
  });
146
226
 
147
- // Handle stderr
227
+ // Handle stderr - pass through (errors, warnings)
148
228
  viteProcess.stderr.on('data', (data) => {
149
229
  process.stderr.write(data);
150
230
  });
@@ -152,20 +232,10 @@ export async function runServe(options = {}) {
152
232
  // Handle process exit
153
233
  return new Promise((resolve) => {
154
234
  viteProcess.on('close', (code) => {
155
- if (code !== 0 && config.server.autoRetry && options._attempt < 10) {
156
- const nextPort = serverConfig.port + 1;
157
- logger.warn(`Port ${serverConfig.port} in use. Trying port ${nextPort}...`);
158
-
159
- // Retry with next port
160
- resolve(runServe({
161
- ...options,
162
- port: nextPort,
163
- _attempt: (options._attempt || 0) + 1,
164
- }));
165
- } else if (code !== 0) {
235
+ if (code !== 0) {
166
236
  resolve({
167
237
  success: false,
168
- error: `Vite exited with code ${code}`,
238
+ error: `Dev server exited with code ${code}`,
169
239
  exitCode: EXIT_CODES.GENERAL_ERROR,
170
240
  });
171
241
  } else {
@@ -178,7 +248,7 @@ export async function runServe(options = {}) {
178
248
  });
179
249
 
180
250
  viteProcess.on('error', (err) => {
181
- logger.error(`Failed to start Vite: ${err.message}`);
251
+ logger.error(`Failed to start dev server: ${err.message}`);
182
252
  resolve({
183
253
  success: false,
184
254
  error: err.message,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quapp",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A lightweight CLI tool for Quapp development - start a Vite dev server with LAN QR code and build .qpp packages for production",
5
5
  "type": "module",
6
6
  "bin": {