quapp 1.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 +29 -0
- package/bin/cli.js +48 -0
- package/build.js +87 -0
- package/package.json +20 -0
- package/server.js +128 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Quapp CLI ๐งช
|
|
2
|
+
|
|
3
|
+
**Quapp CLI** is a developer-friendly CLI tool to serve and Build quapps in a quick and Easy manner
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ๐ Features
|
|
8
|
+
|
|
9
|
+
- โก Create Quapp's Quickly and Efficently
|
|
10
|
+
- ๐ฑ Serve locally and share via LAN QR code
|
|
11
|
+
- ๐ Lightweight and extensible project setup
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ๐ฆ Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install quapp
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
## ๐ง Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
quapp serve
|
|
25
|
+
|
|
26
|
+
quapp build
|
|
27
|
+
|
|
28
|
+
quapp
|
|
29
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
// Resolve __dirname in ESM
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Get user command
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0];
|
|
14
|
+
|
|
15
|
+
// Path to files inside your package
|
|
16
|
+
const buildPath = path.join(__dirname, "../build.js");
|
|
17
|
+
const servePath = path.join(__dirname, "../server.js");
|
|
18
|
+
|
|
19
|
+
// Helper to run a file with Node
|
|
20
|
+
function runScript(scriptPath) {
|
|
21
|
+
const child = spawn("node", [scriptPath], { stdio: "inherit" });
|
|
22
|
+
child.on("close", (code) => process.exit(code));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch (command) {
|
|
26
|
+
case "build":
|
|
27
|
+
runScript(buildPath);
|
|
28
|
+
break;
|
|
29
|
+
case "serve":
|
|
30
|
+
runScript(servePath);
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
console.log(`
|
|
34
|
+
\x1b[1m\x1b[34mQuapp CLI\x1b[0m
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
quapp build Run production build and compress to dist.quapp
|
|
38
|
+
quapp serve Start local server for testing your app
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-h, --help Show this help message
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
quapp build
|
|
45
|
+
quapp serve
|
|
46
|
+
`);
|
|
47
|
+
break;
|
|
48
|
+
}
|
package/build.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { rm } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import archiver from "archiver";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
// ASCII color codes for console
|
|
9
|
+
const c = {
|
|
10
|
+
blue: "\x1b[34m",
|
|
11
|
+
red: "\x1b[31m",
|
|
12
|
+
green: "\x1b[32m",
|
|
13
|
+
yellow: "\x1b[33m",
|
|
14
|
+
reset: "\x1b[0m",
|
|
15
|
+
bold: "\x1b[1m",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// __dirname workaround for ESM
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
|
|
22
|
+
// Paths
|
|
23
|
+
const projectRoot = process.cwd();
|
|
24
|
+
const distFolder = path.join(projectRoot, "dist");
|
|
25
|
+
const outputQpp = path.join(projectRoot, "dist.qpp");
|
|
26
|
+
|
|
27
|
+
console.log(`${c.blue}\n๐ฆ Starting production build...${c.reset}`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Step 1: Run build
|
|
31
|
+
try {
|
|
32
|
+
execSync("npm run build", { stdio: "inherit" });
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(`${c.red}โ Build process failed. Please check your build script.${c.reset}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Step 2: Verify dist/ exists
|
|
39
|
+
if (!fs.existsSync(distFolder)) {
|
|
40
|
+
console.error(`${c.red}โ Build folder 'dist/' not found!${c.reset}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Step 3: Compress dist/ into dist.qpp using archiver
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
const output = fs.createWriteStream(outputQpp);
|
|
47
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
48
|
+
|
|
49
|
+
output.on("close", () => {
|
|
50
|
+
console.log(`${c.green}\nโ
Project built and compressed โ ${c.bold}dist.qpp${c.reset}`);
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
output.on("error", (err) => {
|
|
55
|
+
console.error(`${c.red}โ Failed to write output file: ${err.message}${c.reset}`);
|
|
56
|
+
reject(err);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
archive.on("warning", (err) => {
|
|
60
|
+
if (err.code === "ENOENT") {
|
|
61
|
+
console.warn(`${c.yellow}โ ๏ธ Archive warning: ${err.message}${c.reset}`);
|
|
62
|
+
} else {
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
archive.on("error", (err) => {
|
|
68
|
+
console.error(`${c.red}โ Archiving failed: ${err.message}${c.reset}`);
|
|
69
|
+
reject(err);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
archive.pipe(output);
|
|
73
|
+
archive.directory(distFolder, false);
|
|
74
|
+
archive.finalize();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Step 4: Remove dist folder
|
|
78
|
+
try {
|
|
79
|
+
await rm(distFolder, { recursive: true, force: true });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn(`${c.yellow}โ ๏ธ Could not remove dist/: ${err.message}${c.reset}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(`${c.red}\nโ Unexpected failure: ${err.message}${c.reset}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quapp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"author": "",
|
|
10
|
+
"bin": {
|
|
11
|
+
"quapp": "./bin/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"description": "",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"archiver": "^7.0.1",
|
|
17
|
+
"open": "^10.1.2",
|
|
18
|
+
"qrcode-terminal": "^0.12.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import qrcode from "qrcode-terminal";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import open from "open";
|
|
7
|
+
|
|
8
|
+
// Load config
|
|
9
|
+
let config = {
|
|
10
|
+
server: {
|
|
11
|
+
qr: true,
|
|
12
|
+
network: "private",
|
|
13
|
+
port: 5173,
|
|
14
|
+
fallbackPort: true,
|
|
15
|
+
https: false,
|
|
16
|
+
openBrowser: false,
|
|
17
|
+
autoRetry: true,
|
|
18
|
+
strictPort: false
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const configPath = path.resolve("quapp.config.json");
|
|
24
|
+
if (fs.existsSync(configPath)) {
|
|
25
|
+
const userConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
26
|
+
config = {
|
|
27
|
+
...config,
|
|
28
|
+
...userConfig,
|
|
29
|
+
server: { ...config.server, ...userConfig.server }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.warn("โ ๏ธ Failed to read quapp.config.json. Using default config.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get local or LAN IP
|
|
37
|
+
function getIP(networkType = "local") {
|
|
38
|
+
if (networkType === "private") {
|
|
39
|
+
const interfaces = os.networkInterfaces();
|
|
40
|
+
for (const key in interfaces) {
|
|
41
|
+
for (const iface of interfaces[key] || []) {
|
|
42
|
+
if (!iface.internal && iface.family === "IPv4") {
|
|
43
|
+
return iface.address;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return "localhost";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Start Vite with fallback logic
|
|
52
|
+
function startVite(port, attempt = 0) {
|
|
53
|
+
const host = config.server.network === "private" ? getIP("private") : "localhost";
|
|
54
|
+
const url = `${config.server.https ? "https" : "http"}://${host}:${port}`;
|
|
55
|
+
|
|
56
|
+
const viteArgs = [
|
|
57
|
+
"--host",
|
|
58
|
+
host,
|
|
59
|
+
"--port",
|
|
60
|
+
port,
|
|
61
|
+
// ๐ Automatically enable strictPort if autoRetry is false
|
|
62
|
+
...(config.server.strictPort || !config.server.autoRetry ? ["--strictPort"] : []),
|
|
63
|
+
...(config.server.https ? ["--https"] : [])
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const viteProcess = spawn("vite", viteArgs, { shell: true });
|
|
67
|
+
|
|
68
|
+
let shown = false;
|
|
69
|
+
|
|
70
|
+
viteProcess.stdout.on("data", (data) => {
|
|
71
|
+
const output = data.toString();
|
|
72
|
+
|
|
73
|
+
const portMatch = output.match(/http[s]?:\/\/.*:(\d+)/);
|
|
74
|
+
if (portMatch && !shown) {
|
|
75
|
+
const usedPort = parseInt(portMatch[1]);
|
|
76
|
+
const finalUrl = `${config.server.https ? "https" : "http"}://${host}:${usedPort}`;
|
|
77
|
+
|
|
78
|
+
console.log(`\n\n๐ Access your app from LAN at: ${finalUrl}`);
|
|
79
|
+
if (config.server.qr) {
|
|
80
|
+
console.log(`\n๐ฑ Scan the QR code below to open on any device:\n`);
|
|
81
|
+
qrcode.generate(finalUrl, { small: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (config.server.openBrowser) {
|
|
85
|
+
open(finalUrl); // โ
Uses external open package
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
shown = true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
process.stdout.write(data);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
viteProcess.stderr.on("data", (data) => {
|
|
95
|
+
process.stderr.write(data);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
viteProcess.on("exit", (code) => {
|
|
99
|
+
if (
|
|
100
|
+
code !== 0 &&
|
|
101
|
+
config.server.fallbackPort &&
|
|
102
|
+
config.server.autoRetry &&
|
|
103
|
+
attempt < 10
|
|
104
|
+
) {
|
|
105
|
+
console.log(`โ ๏ธ Port ${port} might be in use. Retrying on port ${port + 1}...`);
|
|
106
|
+
startVite(port + 1, attempt + 1);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(`โ Vite exited with code ${code}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Check if Vite is installed
|
|
113
|
+
function checkViteInstalled() {
|
|
114
|
+
try {
|
|
115
|
+
require.resolve("vite");
|
|
116
|
+
return true;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Check if Vite is installed
|
|
122
|
+
if (checkViteInstalled()) {
|
|
123
|
+
console.error("โ Vite is not installed. Please install it globally or in your project.");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Kickoff
|
|
128
|
+
startVite(config.server.port);
|