afront 1.0.26 → 1.0.28
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/install.js +235 -56
- package/package.json +4 -1
package/install.js
CHANGED
|
@@ -7,13 +7,46 @@ const os = require("os");
|
|
|
7
7
|
const AdmZip = require("adm-zip");
|
|
8
8
|
const readline = require("readline");
|
|
9
9
|
const chalk = require("chalk");
|
|
10
|
+
const semver = require("semver");
|
|
11
|
+
const cliProgress = require("cli-progress");
|
|
12
|
+
const packageJson = require("./package.json");
|
|
10
13
|
|
|
11
14
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "afront-"));
|
|
12
|
-
const VERSION =
|
|
15
|
+
const VERSION = packageJson.version;
|
|
16
|
+
const MIN_NODE_VERSION = "20.18.1";
|
|
17
|
+
|
|
18
|
+
if (!semver.gte(process.version, MIN_NODE_VERSION)) {
|
|
19
|
+
console.error(
|
|
20
|
+
chalk.red(
|
|
21
|
+
`\n✖ AFront requires Node.js ${MIN_NODE_VERSION} or higher.\nCurrent: ${process.version}\n`,
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
process.on("SIGINT", () => {
|
|
28
|
+
console.log(chalk.yellow("\n\nInstallation cancelled."));
|
|
29
|
+
rl.close();
|
|
30
|
+
cleanup();
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.on("uncaughtException", (err) => {
|
|
35
|
+
console.error(chalk.red("\nUnexpected error:\n"), err);
|
|
36
|
+
rl.close();
|
|
37
|
+
cleanup();
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
process.on("unhandledRejection", (err) => {
|
|
42
|
+
console.error(chalk.red("\nUnhandled promise rejection:\n"), err);
|
|
43
|
+
rl.close();
|
|
44
|
+
cleanup();
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
13
47
|
|
|
14
48
|
// Configuration
|
|
15
|
-
const GITHUB_ZIP_URL =
|
|
16
|
-
"https://github.com/Asggen/afront/archive/refs/tags/v1.0.26.zip"; // Updated URL
|
|
49
|
+
const GITHUB_ZIP_URL = `https://codeload.github.com/Asggen/afront/zip/refs/tags/v${VERSION}`;
|
|
17
50
|
|
|
18
51
|
// Define files to skip
|
|
19
52
|
const SKIP_FILES = [
|
|
@@ -30,14 +63,30 @@ const rl = readline.createInterface({
|
|
|
30
63
|
output: process.stdout,
|
|
31
64
|
});
|
|
32
65
|
|
|
66
|
+
const cleanup = () => {
|
|
67
|
+
try {
|
|
68
|
+
if (fs.existsSync(tmpDir)) {
|
|
69
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error("Error cleaning up temporary directory:", err);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
33
76
|
// Spinner function
|
|
34
77
|
const spinner = (text, delay = 100) => {
|
|
78
|
+
if (!process.stdout.isTTY) {
|
|
79
|
+
console.log(text);
|
|
80
|
+
return () => {};
|
|
81
|
+
}
|
|
82
|
+
|
|
35
83
|
const spinnerChars = ["|", "/", "-", "\\"];
|
|
36
84
|
let i = 0;
|
|
85
|
+
|
|
37
86
|
const interval = setInterval(() => {
|
|
38
87
|
readline.cursorTo(process.stdout, 0);
|
|
39
88
|
process.stdout.write(`${text} ${spinnerChars[i++]}`);
|
|
40
|
-
i
|
|
89
|
+
i %= spinnerChars.length;
|
|
41
90
|
}, delay);
|
|
42
91
|
|
|
43
92
|
return () => {
|
|
@@ -58,43 +107,103 @@ const askQuestion = (question) => {
|
|
|
58
107
|
const downloadFile = (url, destination) => {
|
|
59
108
|
return new Promise((resolve, reject) => {
|
|
60
109
|
const file = fs.createWriteStream(destination);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
response.statusCode >= 300 &&
|
|
66
|
-
response.statusCode < 400 &&
|
|
67
|
-
response.headers.location
|
|
68
|
-
) {
|
|
69
|
-
return downloadFile(response.headers.location, destination)
|
|
70
|
-
.then(resolve)
|
|
71
|
-
.catch(reject);
|
|
72
|
-
}
|
|
110
|
+
file.on("error", (err) => {
|
|
111
|
+
reject(err);
|
|
112
|
+
});
|
|
73
113
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
114
|
+
https
|
|
115
|
+
.get(url, (response) => {
|
|
116
|
+
if (
|
|
117
|
+
response.statusCode >= 300 &&
|
|
118
|
+
response.statusCode < 400 &&
|
|
119
|
+
response.headers.location
|
|
120
|
+
) {
|
|
121
|
+
return downloadFile(response.headers.location, destination)
|
|
122
|
+
.then(resolve)
|
|
123
|
+
.catch(reject);
|
|
124
|
+
}
|
|
82
125
|
|
|
83
|
-
|
|
126
|
+
if (response.statusCode !== 200) {
|
|
127
|
+
return reject(
|
|
128
|
+
new Error(
|
|
129
|
+
`Failed to download file. Status code: ${response.statusCode}`,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
84
133
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
const totalHeader = response.headers["content-length"];
|
|
135
|
+
const total = totalHeader ? Number(totalHeader) : null;
|
|
136
|
+
|
|
137
|
+
let bar;
|
|
138
|
+
let downloaded = 0;
|
|
139
|
+
|
|
140
|
+
const useTTY = process.stdout.isTTY;
|
|
141
|
+
|
|
142
|
+
const formatBytes = (bytes) => {
|
|
143
|
+
if (bytes < 1024) return bytes + " B";
|
|
144
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
145
|
+
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (useTTY && total) {
|
|
149
|
+
let downloadedBytes = 0;
|
|
150
|
+
const totalFormatted = formatBytes(total);
|
|
151
|
+
|
|
152
|
+
bar = new cliProgress.SingleBar({
|
|
153
|
+
format: (options, params) => {
|
|
154
|
+
const percentage = Math.floor(
|
|
155
|
+
(params.value / params.total) * 100,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const termWidth = process.stdout.columns || 80;
|
|
159
|
+
const barLength = Math.max(10, Math.min(termWidth - 60, 30));
|
|
160
|
+
const filledLength = Math.round(barLength * params.progress);
|
|
161
|
+
|
|
162
|
+
const barVisual =
|
|
163
|
+
"█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
164
|
+
|
|
165
|
+
const downloadedFormatted = formatBytes(params.value);
|
|
166
|
+
const totalFormatted = formatBytes(params.total);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
chalk.cyan("⬇ Downloading template ") +
|
|
170
|
+
chalk.green(barVisual) +
|
|
171
|
+
chalk.white(` ${percentage}%`) +
|
|
172
|
+
chalk.gray(` (${downloadedFormatted} / ${totalFormatted})`)
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
hideCursor: true,
|
|
176
|
+
clearOnComplete: false,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
bar.start(total, 0);
|
|
180
|
+
|
|
181
|
+
response.on("data", (chunk) => {
|
|
182
|
+
downloadedBytes += chunk.length;
|
|
183
|
+
bar.update(downloadedBytes);
|
|
184
|
+
});
|
|
185
|
+
} else {
|
|
186
|
+
console.log(chalk.cyan("⬇ Downloading template..."));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
response.pipe(file);
|
|
190
|
+
|
|
191
|
+
file.on("finish", () => {
|
|
192
|
+
file.close(() => {
|
|
193
|
+
if (bar) {
|
|
194
|
+
bar.stop();
|
|
195
|
+
const totalFormatted = formatBytes(total);
|
|
196
|
+
console.log(
|
|
197
|
+
chalk.green(`✔ Downloaded template (${totalFormatted})`),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
resolve();
|
|
201
|
+
});
|
|
89
202
|
});
|
|
203
|
+
})
|
|
204
|
+
.on("error", (err) => {
|
|
205
|
+
reject(err);
|
|
90
206
|
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
request.on("error", (err) => {
|
|
94
|
-
stopSpinner();
|
|
95
|
-
fs.promises.unlink(destination).catch(() => {});
|
|
96
|
-
reject(err);
|
|
97
|
-
});
|
|
98
207
|
});
|
|
99
208
|
};
|
|
100
209
|
|
|
@@ -124,42 +233,91 @@ const extractZip = (zipPath, extractTo) => {
|
|
|
124
233
|
|
|
125
234
|
const runNpmInstall = (directory) => {
|
|
126
235
|
return new Promise((resolve, reject) => {
|
|
127
|
-
const
|
|
236
|
+
const isWindows = process.platform === "win32";
|
|
237
|
+
|
|
238
|
+
// ✅ Non-interactive (CI / Docker / GitHub Actions)
|
|
239
|
+
if (!process.stdout.isTTY) {
|
|
240
|
+
const child = spawn(
|
|
241
|
+
isWindows ? "npm.cmd" : "npm",
|
|
242
|
+
["install", "--legacy-peer-deps"],
|
|
243
|
+
{
|
|
244
|
+
cwd: directory,
|
|
245
|
+
stdio: "inherit",
|
|
246
|
+
shell: isWindows,
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
child.on("close", (code) =>
|
|
251
|
+
code === 0 ? resolve() : reject(new Error("npm install failed")),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
child.on("error", reject);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ✅ Interactive terminal (with progress bar)
|
|
259
|
+
console.log();
|
|
260
|
+
|
|
261
|
+
const bar = new cliProgress.SingleBar(
|
|
262
|
+
{
|
|
263
|
+
format:
|
|
264
|
+
chalk.magenta("📦 Installing dependencies ") +
|
|
265
|
+
chalk.green("{bar}") +
|
|
266
|
+
chalk.white(" {percentage}%"),
|
|
267
|
+
barCompleteChar: "█",
|
|
268
|
+
barIncompleteChar: "░",
|
|
269
|
+
hideCursor: true,
|
|
270
|
+
clearOnComplete: false,
|
|
271
|
+
},
|
|
272
|
+
cliProgress.Presets.shades_classic,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
bar.start(100, 0);
|
|
276
|
+
|
|
277
|
+
let progress = 0;
|
|
278
|
+
|
|
279
|
+
// Smooth simulated progress
|
|
280
|
+
const interval = setInterval(() => {
|
|
281
|
+
if (progress < 90) {
|
|
282
|
+
progress += Math.random() * 5;
|
|
283
|
+
bar.update(Math.floor(progress));
|
|
284
|
+
}
|
|
285
|
+
}, 200);
|
|
128
286
|
|
|
129
287
|
const child = spawn(
|
|
130
|
-
|
|
288
|
+
isWindows ? "npm.cmd" : "npm",
|
|
131
289
|
[
|
|
132
290
|
"install",
|
|
133
291
|
"--legacy-peer-deps",
|
|
134
292
|
"--no-audit",
|
|
135
293
|
"--no-fund",
|
|
294
|
+
"--no-progress",
|
|
136
295
|
"--loglevel=error",
|
|
137
296
|
],
|
|
138
297
|
{
|
|
139
298
|
cwd: directory,
|
|
140
|
-
stdio: "
|
|
141
|
-
shell:
|
|
299
|
+
stdio: "ignore",
|
|
300
|
+
shell: isWindows,
|
|
142
301
|
},
|
|
143
302
|
);
|
|
144
303
|
|
|
145
|
-
let errorOutput = "";
|
|
146
|
-
|
|
147
|
-
child.stderr.on("data", (data) => {
|
|
148
|
-
errorOutput += data.toString();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
304
|
child.on("close", (code) => {
|
|
152
|
-
|
|
305
|
+
clearInterval(interval);
|
|
306
|
+
|
|
153
307
|
if (code === 0) {
|
|
308
|
+
bar.update(100);
|
|
309
|
+
bar.stop();
|
|
310
|
+
console.log();
|
|
154
311
|
resolve();
|
|
155
312
|
} else {
|
|
156
|
-
|
|
157
|
-
reject(new Error(`npm
|
|
313
|
+
bar.stop();
|
|
314
|
+
reject(new Error(`npm exited with code ${code}`));
|
|
158
315
|
}
|
|
159
316
|
});
|
|
160
317
|
|
|
161
318
|
child.on("error", (err) => {
|
|
162
|
-
|
|
319
|
+
clearInterval(interval);
|
|
320
|
+
bar.stop();
|
|
163
321
|
reject(err);
|
|
164
322
|
});
|
|
165
323
|
});
|
|
@@ -394,6 +552,14 @@ const main = async () => {
|
|
|
394
552
|
|
|
395
553
|
folderName = sanitizeFolderName(folderName);
|
|
396
554
|
|
|
555
|
+
if (folderName === path.basename(process.cwd())) {
|
|
556
|
+
console.log(
|
|
557
|
+
chalk.yellow(
|
|
558
|
+
"\nYou are creating a project inside the current directory.\n",
|
|
559
|
+
),
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
397
563
|
console.log(
|
|
398
564
|
chalk.green(`Creating project in ${chalk.bold(`./${folderName}`)}\n`),
|
|
399
565
|
);
|
|
@@ -422,12 +588,14 @@ const main = async () => {
|
|
|
422
588
|
|
|
423
589
|
const extractedFolderName = extractedItems.find((item) => {
|
|
424
590
|
const fullPath = path.join(tmpDir, item);
|
|
425
|
-
|
|
591
|
+
|
|
592
|
+
return fs.lstatSync(fullPath).isDirectory() && item.startsWith("afront-");
|
|
426
593
|
});
|
|
427
594
|
|
|
428
595
|
if (!extractedFolderName) {
|
|
429
|
-
throw new Error("Extraction failed:
|
|
596
|
+
throw new Error("Extraction failed: extracted folder not found.");
|
|
430
597
|
}
|
|
598
|
+
|
|
431
599
|
const extractedFolderPath = path.join(tmpDir, extractedFolderName);
|
|
432
600
|
|
|
433
601
|
await moveFiles(extractedFolderPath, destDir, tmpDir);
|
|
@@ -443,6 +611,10 @@ const main = async () => {
|
|
|
443
611
|
// ignore
|
|
444
612
|
}
|
|
445
613
|
|
|
614
|
+
if (!fs.existsSync(path.join(destDir, "package.json"))) {
|
|
615
|
+
throw new Error("package.json not found after extraction.");
|
|
616
|
+
}
|
|
617
|
+
|
|
446
618
|
await runNpmInstall(destDir);
|
|
447
619
|
|
|
448
620
|
console.log(chalk.green("\n✔ AFront project created successfully!\n"));
|
|
@@ -454,8 +626,7 @@ const main = async () => {
|
|
|
454
626
|
console.log(chalk.gray("Happy building with AFront 🚀"));
|
|
455
627
|
|
|
456
628
|
rl.close();
|
|
457
|
-
|
|
458
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
629
|
+
cleanup();
|
|
459
630
|
process.exit(0);
|
|
460
631
|
} catch (err) {
|
|
461
632
|
console.error(chalk.red("✖ Error:"), err.message);
|
|
@@ -514,5 +685,13 @@ const showBanner = () => {
|
|
|
514
685
|
console.log(chalk.gray("🚀 https://afront.dev\n"));
|
|
515
686
|
};
|
|
516
687
|
|
|
517
|
-
|
|
518
|
-
|
|
688
|
+
(async () => {
|
|
689
|
+
try {
|
|
690
|
+
showBanner();
|
|
691
|
+
await main();
|
|
692
|
+
} catch (err) {
|
|
693
|
+
console.error(chalk.red("✖ Fatal Error:"), err.message);
|
|
694
|
+
cleanup();
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "afront",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "AFront is a front-end JavaScript library designed to create seamless server-side rendered (SSSR) websites.",
|
|
5
5
|
"main": "webpack.dev.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"babel-plugin-css-modules-transform": "^1.6.2",
|
|
43
43
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
44
44
|
"banner-webpack-after-content": "^1.0.3",
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
|
+
"cli-progress": "^3.12.0",
|
|
45
47
|
"copy-webpack-plugin": "^12.0.2",
|
|
46
48
|
"csp-html-webpack-plugin": "^5.1.0",
|
|
47
49
|
"dotenv": "^16.4.5",
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
"loader-utils": "^3.3.1",
|
|
56
58
|
"react-router": "^7.12.0",
|
|
57
59
|
"readline-sync": "^1.4.10",
|
|
60
|
+
"semver": "^7.7.4",
|
|
58
61
|
"styled-components": "^6.1.13",
|
|
59
62
|
"terser-webpack-plugin": "^5.3.10",
|
|
60
63
|
"tmp": "^0.2.3"
|