quapp 1.0.5 โ 1.1.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.
- package/README.md +194 -29
- package/bin/cli.js +134 -48
- package/commands/build.js +267 -0
- package/commands/init.js +201 -0
- package/commands/serve.js +189 -0
- package/lib/args.js +351 -0
- package/lib/colors.js +33 -0
- package/lib/config.js +135 -0
- package/lib/constants.js +33 -0
- package/lib/logger.js +84 -0
- package/lib/manifest.js +89 -0
- package/package.json +57 -31
- package/build.js +0 -155
- package/server.js +0 -131
package/README.md
CHANGED
|
@@ -1,29 +1,194 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
# quapp
|
|
2
|
+
|
|
3
|
+
Development CLI for Quapp projects - start a dev server with LAN QR code and build `.qpp` packages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D quapp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### `quapp init`
|
|
14
|
+
|
|
15
|
+
Initialize Quapp in an existing project. Creates config and adds scripts.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
quapp init [options]
|
|
19
|
+
```
|
|
20
|
+
|
|
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 |
|
|
27
|
+
|
|
28
|
+
### `quapp serve`
|
|
29
|
+
|
|
30
|
+
Start development server with LAN access and QR code for mobile testing.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
quapp serve [options]
|
|
34
|
+
```
|
|
35
|
+
|
|
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 |
|
|
44
|
+
|
|
45
|
+
### `quapp build`
|
|
46
|
+
|
|
47
|
+
Build for production and create `.qpp` package.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
quapp build [options]
|
|
51
|
+
```
|
|
52
|
+
|
|
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 |
|
|
59
|
+
|
|
60
|
+
## Global Options
|
|
61
|
+
|
|
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 |
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
Create `quapp.config.json` in your project root:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"server": {
|
|
77
|
+
"port": 5173,
|
|
78
|
+
"qr": true,
|
|
79
|
+
"network": "private",
|
|
80
|
+
"openBrowser": false,
|
|
81
|
+
"https": false,
|
|
82
|
+
"fallbackPort": true,
|
|
83
|
+
"autoRetry": true,
|
|
84
|
+
"strictPort": false
|
|
85
|
+
},
|
|
86
|
+
"build": {
|
|
87
|
+
"outDir": "dist",
|
|
88
|
+
"outputFile": "dist.qpp"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
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
|
|
116
|
+
|
|
117
|
+
# Build with JSON output (AI-friendly)
|
|
118
|
+
quapp build --json --skip-prompts
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## AI/Automation Usage
|
|
122
|
+
|
|
123
|
+
For non-interactive environments:
|
|
124
|
+
|
|
125
|
+
```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
|
|
137
|
+
```
|
|
138
|
+
|
|
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
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"success": true,
|
|
154
|
+
"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\" }"
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Exit Codes
|
|
176
|
+
|
|
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 |
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- Node.js >= 18.0.0
|
|
190
|
+
- Vite (in your project's dependencies)
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -1,48 +1,134 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* quapp CLI - Development and build tools for Quapp projects
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* quapp serve - Start development server with LAN access
|
|
8
|
+
* quapp build - Build for production and create .qpp package
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parseArgs, getHelpText, getVersion } from '../lib/args.js';
|
|
12
|
+
import { initColors } from '../lib/colors.js';
|
|
13
|
+
import * as logger from '../lib/logger.js';
|
|
14
|
+
import { EXIT_CODES } from '../lib/constants.js';
|
|
15
|
+
import { runServe } from '../commands/serve.js';
|
|
16
|
+
import { runBuild } from '../commands/build.js';
|
|
17
|
+
import { runInit } from '../commands/init.js';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Main Entry Point
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
// Parse arguments
|
|
25
|
+
const args = parseArgs(process.argv.slice(2));
|
|
26
|
+
|
|
27
|
+
// Handle --help
|
|
28
|
+
if (args.help) {
|
|
29
|
+
console.log(getHelpText());
|
|
30
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle --version
|
|
34
|
+
if (args.version) {
|
|
35
|
+
console.log(getVersion());
|
|
36
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Initialize colors
|
|
40
|
+
initColors(args.noColor);
|
|
41
|
+
|
|
42
|
+
// Initialize logger
|
|
43
|
+
logger.initLogger({ json: args.json, verbose: args.verbose });
|
|
44
|
+
|
|
45
|
+
// Check for argument errors
|
|
46
|
+
if (args.errors.length > 0) {
|
|
47
|
+
for (const err of args.errors) {
|
|
48
|
+
logger.error(err);
|
|
49
|
+
}
|
|
50
|
+
if (args.json) {
|
|
51
|
+
logger.outputJson({ success: false, errors: args.errors });
|
|
52
|
+
}
|
|
53
|
+
process.exit(EXIT_CODES.INVALID_ARGS);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle missing command
|
|
57
|
+
if (!args.command) {
|
|
58
|
+
console.log(getHelpText());
|
|
59
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Route to command
|
|
63
|
+
let result;
|
|
64
|
+
|
|
65
|
+
switch (args.command) {
|
|
66
|
+
case 'serve':
|
|
67
|
+
result = await runServe({
|
|
68
|
+
port: args.port,
|
|
69
|
+
host: args.host,
|
|
70
|
+
qr: args.qr,
|
|
71
|
+
open: args.open,
|
|
72
|
+
https: args.https,
|
|
73
|
+
extra: args.extra,
|
|
74
|
+
_attempt: 0,
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'build':
|
|
79
|
+
result = await runBuild({
|
|
80
|
+
output: args.output,
|
|
81
|
+
clean: args.clean,
|
|
82
|
+
skipPrompts: args.skipPrompts,
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'init':
|
|
87
|
+
result = await runInit({
|
|
88
|
+
force: args.force,
|
|
89
|
+
yes: args.yes,
|
|
90
|
+
dryRun: args.dryRun,
|
|
91
|
+
skipPrompts: args.skipPrompts,
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
logger.error(`Unknown command: ${args.command}`);
|
|
97
|
+
logger.info('Run "quapp --help" for available commands');
|
|
98
|
+
|
|
99
|
+
if (args.json) {
|
|
100
|
+
logger.outputJson({ success: false, error: `Unknown command: ${args.command}` });
|
|
101
|
+
}
|
|
102
|
+
process.exit(EXIT_CODES.INVALID_ARGS);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Output JSON result if requested
|
|
106
|
+
if (args.json && result) {
|
|
107
|
+
logger.outputJson(result);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Exit with appropriate code
|
|
111
|
+
process.exit(result?.success ? EXIT_CODES.SUCCESS : (result?.exitCode || EXIT_CODES.GENERAL_ERROR));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Run
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
main().catch((err) => {
|
|
119
|
+
console.error(`\x1b[31mโ Unexpected error: ${err.message}\x1b[0m`);
|
|
120
|
+
|
|
121
|
+
if (process.argv.includes('--verbose')) {
|
|
122
|
+
console.error(err.stack);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (process.argv.includes('--json')) {
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
success: false,
|
|
128
|
+
error: err.message,
|
|
129
|
+
stack: process.argv.includes('--verbose') ? err.stack : undefined,
|
|
130
|
+
}, null, 2));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
134
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build command - Create production .qpp package
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { rm } from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import archiver from 'archiver';
|
|
10
|
+
import prompts from 'prompts';
|
|
11
|
+
import * as logger from '../lib/logger.js';
|
|
12
|
+
import { loadConfig, loadPackageJson, updatePackageJson, hasBuildScript } from '../lib/config.js';
|
|
13
|
+
import { generateManifest, writeManifest } from '../lib/manifest.js';
|
|
14
|
+
import { EXIT_CODES } from '../lib/constants.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Prompt for missing package.json fields
|
|
18
|
+
* @param {Object} pkg - Current package.json
|
|
19
|
+
* @param {string[]} missing - List of missing fields
|
|
20
|
+
* @returns {Promise<Object>} Updated values
|
|
21
|
+
*/
|
|
22
|
+
async function promptMissingFields(pkg, missing) {
|
|
23
|
+
const questions = [];
|
|
24
|
+
|
|
25
|
+
if (missing.includes('name')) {
|
|
26
|
+
questions.push({
|
|
27
|
+
type: 'text',
|
|
28
|
+
name: 'name',
|
|
29
|
+
message: 'Enter project name:',
|
|
30
|
+
validate: (x) => x.trim() !== '' ? true : 'Name is required',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (missing.includes('version')) {
|
|
35
|
+
questions.push({
|
|
36
|
+
type: 'text',
|
|
37
|
+
name: 'version',
|
|
38
|
+
message: 'Enter version (e.g., 1.0.0):',
|
|
39
|
+
initial: '1.0.0',
|
|
40
|
+
validate: (x) => x.trim() !== '' ? true : 'Version is required',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Always ask for author if not present
|
|
45
|
+
if (!pkg.author) {
|
|
46
|
+
questions.push({
|
|
47
|
+
type: 'text',
|
|
48
|
+
name: 'author',
|
|
49
|
+
message: 'Enter author name:',
|
|
50
|
+
validate: (x) => x.trim() !== '' ? true : 'Author is required',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (questions.length === 0) {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return await prompts(questions);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Compress directory to .qpp file
|
|
63
|
+
* @param {string} sourceDir - Directory to compress
|
|
64
|
+
* @param {string} outputPath - Output file path
|
|
65
|
+
* @returns {Promise<Object>} Result with file size
|
|
66
|
+
*/
|
|
67
|
+
async function compressToQpp(sourceDir, outputPath) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const output = fs.createWriteStream(outputPath);
|
|
70
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
71
|
+
|
|
72
|
+
output.on('close', () => {
|
|
73
|
+
resolve({
|
|
74
|
+
success: true,
|
|
75
|
+
size: archive.pointer(),
|
|
76
|
+
path: outputPath,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
archive.on('error', (err) => {
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
archive.pipe(output);
|
|
85
|
+
archive.directory(sourceDir, false);
|
|
86
|
+
archive.finalize();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Format bytes to human readable string
|
|
92
|
+
* @param {number} bytes
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function formatSize(bytes) {
|
|
96
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
97
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
98
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Run the build command
|
|
103
|
+
* @param {Object} options - Command options
|
|
104
|
+
* @returns {Promise<Object>} Result
|
|
105
|
+
*/
|
|
106
|
+
export async function runBuild(options = {}) {
|
|
107
|
+
const cwd = process.cwd();
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
|
|
110
|
+
// Load config
|
|
111
|
+
const { config, configError } = loadConfig(cwd);
|
|
112
|
+
|
|
113
|
+
if (configError) {
|
|
114
|
+
logger.warn(configError);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Load package.json
|
|
118
|
+
const pkgResult = loadPackageJson(cwd);
|
|
119
|
+
|
|
120
|
+
if (!pkgResult.success) {
|
|
121
|
+
logger.error(pkgResult.error);
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
errorCode: 'PACKAGE_JSON_ERROR',
|
|
125
|
+
error: pkgResult.error,
|
|
126
|
+
suggestion: 'Make sure you are in a Quapp project directory',
|
|
127
|
+
exitCode: EXIT_CODES.CONFIG_ERROR
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let pkg = pkgResult.package;
|
|
132
|
+
|
|
133
|
+
// Handle missing fields
|
|
134
|
+
if (pkgResult.missingFields.length > 0 || !pkg.author) {
|
|
135
|
+
if (options.skipPrompts) {
|
|
136
|
+
// In non-interactive mode, use defaults for missing fields
|
|
137
|
+
const updates = {};
|
|
138
|
+
|
|
139
|
+
if (pkgResult.missingFields.includes('name')) {
|
|
140
|
+
logger.error('Missing required field: name');
|
|
141
|
+
return { success: false, error: 'Missing name in package.json', exitCode: EXIT_CODES.CONFIG_ERROR };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (pkgResult.missingFields.includes('version')) {
|
|
145
|
+
updates.version = '1.0.0';
|
|
146
|
+
logger.warn('No version specified, using 1.0.0');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!pkg.author) {
|
|
150
|
+
updates.author = 'developer';
|
|
151
|
+
logger.warn('No author specified, using "developer"');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (Object.keys(updates).length > 0) {
|
|
155
|
+
const updateResult = updatePackageJson(cwd, updates);
|
|
156
|
+
if (updateResult.success) {
|
|
157
|
+
pkg = updateResult.package;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
// Interactive mode - prompt for missing fields
|
|
162
|
+
const answers = await promptMissingFields(pkg, pkgResult.missingFields);
|
|
163
|
+
|
|
164
|
+
if (Object.keys(answers).length > 0) {
|
|
165
|
+
const updateResult = updatePackageJson(cwd, answers);
|
|
166
|
+
if (!updateResult.success) {
|
|
167
|
+
logger.error(updateResult.error);
|
|
168
|
+
return { success: false, error: updateResult.error, exitCode: EXIT_CODES.CONFIG_ERROR };
|
|
169
|
+
}
|
|
170
|
+
pkg = updateResult.package;
|
|
171
|
+
logger.success('Updated package.json');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for build script
|
|
177
|
+
if (!hasBuildScript(pkg)) {
|
|
178
|
+
logger.error('No "build" script found in package.json');
|
|
179
|
+
logger.info('Add a build script to your package.json, e.g.: "build": "vite build"');
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
errorCode: 'NO_BUILD_SCRIPT',
|
|
183
|
+
error: 'No build script',
|
|
184
|
+
suggestion: 'Add to package.json: "scripts": { "build": "vite build" }',
|
|
185
|
+
exitCode: EXIT_CODES.CONFIG_ERROR
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Paths
|
|
190
|
+
const distDir = path.join(cwd, config.build.outDir);
|
|
191
|
+
let outputFile = options.output || config.build.outputFile;
|
|
192
|
+
// Ensure .qpp extension
|
|
193
|
+
if (!outputFile.endsWith('.qpp')) {
|
|
194
|
+
outputFile = `${outputFile}.qpp`;
|
|
195
|
+
}
|
|
196
|
+
const outputPath = path.join(cwd, outputFile);
|
|
197
|
+
|
|
198
|
+
// Step 1: Run build
|
|
199
|
+
logger.step('๐ฆ', 'Building for production...');
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
execSync('npm run build', {
|
|
203
|
+
cwd,
|
|
204
|
+
stdio: logger.isJsonMode() ? 'pipe' : 'inherit'
|
|
205
|
+
});
|
|
206
|
+
logger.success('Build completed');
|
|
207
|
+
} catch (err) {
|
|
208
|
+
logger.error('Build failed');
|
|
209
|
+
return { success: false, error: 'Build failed', exitCode: EXIT_CODES.BUILD_FAILED };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Step 2: Verify dist folder exists
|
|
213
|
+
if (!fs.existsSync(distDir)) {
|
|
214
|
+
logger.error(`Build output directory "${config.build.outDir}" not found`);
|
|
215
|
+
logger.info('Make sure your build script outputs to the correct directory');
|
|
216
|
+
return { success: false, error: 'Build output not found', exitCode: EXIT_CODES.BUILD_FAILED };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Step 3: Generate and write manifest
|
|
220
|
+
logger.step('๐', 'Generating manifest...');
|
|
221
|
+
|
|
222
|
+
const manifest = generateManifest(pkg);
|
|
223
|
+
const manifestResult = writeManifest(distDir, manifest);
|
|
224
|
+
|
|
225
|
+
if (!manifestResult.success) {
|
|
226
|
+
logger.error(manifestResult.error);
|
|
227
|
+
return { success: false, error: manifestResult.error, exitCode: EXIT_CODES.GENERAL_ERROR };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
logger.success('Manifest created');
|
|
231
|
+
logger.debug(`Package: ${manifest.package_name}`);
|
|
232
|
+
logger.debug(`Version: ${manifest.version} (code: ${manifest.version_code})`);
|
|
233
|
+
|
|
234
|
+
// Step 4: Compress to .qpp
|
|
235
|
+
logger.step('๐๏ธ', `Compressing to ${outputFile}...`);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const compressResult = await compressToQpp(distDir, outputPath);
|
|
239
|
+
logger.success(`Created ${outputFile} (${formatSize(compressResult.size)})`);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.error(`Failed to create ${outputFile}: ${err.message}`);
|
|
242
|
+
return { success: false, error: 'Compression failed', exitCode: EXIT_CODES.GENERAL_ERROR };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Step 5: Clean up dist folder
|
|
246
|
+
if (options.clean !== false) {
|
|
247
|
+
try {
|
|
248
|
+
await rm(distDir, { recursive: true, force: true });
|
|
249
|
+
logger.debug('Cleaned up dist folder');
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logger.warn(`Could not remove dist folder: ${err.message}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Done
|
|
256
|
+
const duration = Date.now() - startTime;
|
|
257
|
+
logger.newline();
|
|
258
|
+
logger.success(`Build complete in ${(duration / 1000).toFixed(1)}s`);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
outputFile,
|
|
263
|
+
outputPath,
|
|
264
|
+
manifest,
|
|
265
|
+
duration,
|
|
266
|
+
};
|
|
267
|
+
}
|