afront 1.0.25 ā 1.0.26
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/.babelrc +13 -13
- package/.env +1 -1
- package/LICENSE +21 -21
- package/README.md +94 -94
- package/build-prod/manifest.json +25 -25
- package/build-prod/offline.html +1023 -1023
- package/build-prod/robots.txt +3 -3
- package/build-prod-static/manifest.json +25 -25
- package/build-prod-static/offline.html +1023 -1023
- package/build-prod-static/robots.txt +3 -3
- package/install.js +518 -415
- package/package.json +102 -102
- package/server.js +40 -40
- package/src/ARoutes/AFRoutes.js +28 -28
- package/src/Api/api.config.js +266 -266
- package/src/Api/login.service.js +44 -44
- package/src/App.js +28 -28
- package/src/Components/Background/MeshGradient.js +18 -18
- package/src/Components/Footer/Footer.js +108 -108
- package/src/Components/Header/Header.js +149 -149
- package/src/Components/Loading/LoadingIndicator.js +12 -12
- package/src/Components/Loading/LoadingIndicator.module.css +34 -34
- package/src/Components/Loading/LoadingSpinner.js +27 -27
- package/src/Components/Loading/LoadingSpinner.module.css +100 -100
- package/src/Components/RequireAuth.js +29 -29
- package/src/LoadingFallback.js +13 -13
- package/src/PageNotFound.js +19 -19
- package/src/Pages/Home.js +50 -50
- package/src/Pages/Signup.js +230 -230
- package/src/Pages/Support.js +68 -68
- package/src/Routes/ARoutes.js +66 -66
- package/src/Routes/ARoutesStatic.js +83 -83
- package/src/Static/appStatic.js +16 -16
- package/src/Static/indexStatic.js +13 -13
- package/src/Style/App.module.css +11 -11
- package/src/Style/MeshGradient.module.css +130 -130
- package/src/Style/PageNotFound.module.css +37 -37
- package/src/Style/Style.module.css +686 -686
- package/src/Style/Support.module.css +185 -185
- package/src/Utils/LoadingContext.js +5 -5
- package/src/index.js +25 -25
- package/webpack.build-prod.js +141 -141
- package/webpack.dev.js +127 -127
- package/webpack.prod.js +148 -148
- package/webpack.ssr.prod.js +97 -97
package/install.js
CHANGED
|
@@ -1,415 +1,518 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const fs = require(
|
|
3
|
-
const path = require(
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
const os = require(
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
const
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
stopSpinner();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
folderName
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const { spawn } = require("child_process");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const AdmZip = require("adm-zip");
|
|
8
|
+
const readline = require("readline");
|
|
9
|
+
const chalk = require("chalk");
|
|
10
|
+
|
|
11
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "afront-"));
|
|
12
|
+
const VERSION = "1.0.26";
|
|
13
|
+
|
|
14
|
+
// Configuration
|
|
15
|
+
const GITHUB_ZIP_URL =
|
|
16
|
+
"https://github.com/Asggen/afront/archive/refs/tags/v1.0.26.zip"; // Updated URL
|
|
17
|
+
|
|
18
|
+
// Define files to skip
|
|
19
|
+
const SKIP_FILES = [
|
|
20
|
+
"FUNDING.yml",
|
|
21
|
+
"CODE_OF_CONDUCT.md",
|
|
22
|
+
"SECURITY.md",
|
|
23
|
+
"install.js",
|
|
24
|
+
".npmrc",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Initialize readline interface
|
|
28
|
+
const rl = readline.createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Spinner function
|
|
34
|
+
const spinner = (text, delay = 100) => {
|
|
35
|
+
const spinnerChars = ["|", "/", "-", "\\"];
|
|
36
|
+
let i = 0;
|
|
37
|
+
const interval = setInterval(() => {
|
|
38
|
+
readline.cursorTo(process.stdout, 0);
|
|
39
|
+
process.stdout.write(`${text} ${spinnerChars[i++]}`);
|
|
40
|
+
i = i % spinnerChars.length;
|
|
41
|
+
}, delay);
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
clearInterval(interval);
|
|
45
|
+
readline.cursorTo(process.stdout, 0);
|
|
46
|
+
process.stdout.write(`${text} Done.\n`);
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const askQuestion = (question) => {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
rl.question(question, (answer) => {
|
|
53
|
+
resolve(answer.trim().toLowerCase());
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const downloadFile = (url, destination) => {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const file = fs.createWriteStream(destination);
|
|
61
|
+
const stopSpinner = spinner(chalk.cyan("⬠Downloading template..."));
|
|
62
|
+
|
|
63
|
+
const request = https.get(url, (response) => {
|
|
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
|
+
}
|
|
73
|
+
|
|
74
|
+
if (response.statusCode !== 200) {
|
|
75
|
+
stopSpinner();
|
|
76
|
+
return reject(
|
|
77
|
+
new Error(
|
|
78
|
+
`Failed to download file. Status code: ${response.statusCode}`,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
response.pipe(file);
|
|
84
|
+
|
|
85
|
+
file.on("finish", () => {
|
|
86
|
+
file.close(() => {
|
|
87
|
+
stopSpinner();
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
request.on("error", (err) => {
|
|
94
|
+
stopSpinner();
|
|
95
|
+
fs.promises.unlink(destination).catch(() => {});
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const extractZip = (zipPath, extractTo) => {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const stopSpinner = spinner(chalk.yellow("š¦ Extracting files..."));
|
|
104
|
+
try {
|
|
105
|
+
const zip = new AdmZip(zipPath);
|
|
106
|
+
zip.extractAllTo(extractTo, true);
|
|
107
|
+
fs.readdir(extractTo, (err, files) => {
|
|
108
|
+
if (err) {
|
|
109
|
+
stopSpinner();
|
|
110
|
+
console.error("Error reading extracted folder:", err);
|
|
111
|
+
reject(err);
|
|
112
|
+
} else {
|
|
113
|
+
stopSpinner();
|
|
114
|
+
resolve();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
stopSpinner();
|
|
119
|
+
console.error("Error extracting zip file:", err);
|
|
120
|
+
reject(err);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const runNpmInstall = (directory) => {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const stopSpinner = spinner(chalk.magenta("š¦ Installing dependencies (this may take a few seconds)..."));
|
|
128
|
+
|
|
129
|
+
const child = spawn(
|
|
130
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
131
|
+
[
|
|
132
|
+
"install",
|
|
133
|
+
"--legacy-peer-deps",
|
|
134
|
+
"--no-audit",
|
|
135
|
+
"--no-fund",
|
|
136
|
+
"--loglevel=error",
|
|
137
|
+
],
|
|
138
|
+
{
|
|
139
|
+
cwd: directory,
|
|
140
|
+
stdio: "pipe",
|
|
141
|
+
shell: false,
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
let errorOutput = "";
|
|
146
|
+
|
|
147
|
+
child.stderr.on("data", (data) => {
|
|
148
|
+
errorOutput += data.toString();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
child.on("close", (code) => {
|
|
152
|
+
stopSpinner();
|
|
153
|
+
if (code === 0) {
|
|
154
|
+
resolve();
|
|
155
|
+
} else {
|
|
156
|
+
console.error(chalk.red(errorOutput));
|
|
157
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
child.on("error", (err) => {
|
|
162
|
+
stopSpinner();
|
|
163
|
+
reject(err);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const createDirIfNotExists = (dirPath) => {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
fs.mkdir(dirPath, { recursive: true }, (err) => {
|
|
171
|
+
if (err) {
|
|
172
|
+
reject(new Error("Error creating directory:", err));
|
|
173
|
+
} else {
|
|
174
|
+
resolve();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const isPathInside = (root, target) => {
|
|
181
|
+
const r = path.resolve(root);
|
|
182
|
+
const t = path.resolve(target);
|
|
183
|
+
return t === r || t.startsWith(r + path.sep);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Security: path validated against SAFE_UNLINK_ROOT before unlink
|
|
187
|
+
const SAFE_UNLINK_ROOT = fs.realpathSync(process.cwd());
|
|
188
|
+
|
|
189
|
+
const safeUnlink = async (targetPath) => {
|
|
190
|
+
const resolved = await fs.promises.realpath(targetPath);
|
|
191
|
+
|
|
192
|
+
if (!resolved.startsWith(SAFE_UNLINK_ROOT + path.sep)) {
|
|
193
|
+
throw new Error(`Blocked unlink outside safe root: ${resolved}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const stats = await fs.promises.lstat(resolved);
|
|
197
|
+
|
|
198
|
+
if (!stats.isFile()) {
|
|
199
|
+
throw new Error(`Refusing to unlink non-file: ${resolved}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await fs.promises.unlink(resolved);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const safeRm = (dirPath, cb) => {
|
|
206
|
+
const resolved = path.resolve(dirPath);
|
|
207
|
+
if (!isPathInside(process.cwd(), resolved)) {
|
|
208
|
+
return cb(
|
|
209
|
+
new Error(
|
|
210
|
+
`Refusing to remove directory outside current working directory: ${resolved}`,
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
fs.rm(resolved, { recursive: true, force: true }, cb);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Perform an atomic-safe move: open source with O_NOFOLLOW, stream to a temp file, fsync, then rename
|
|
218
|
+
const safeMoveFile = async (resolvedSrc, resolvedDest) => {
|
|
219
|
+
const srcFlags =
|
|
220
|
+
process.platform === "win32"
|
|
221
|
+
? fs.constants.O_RDONLY
|
|
222
|
+
: fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;
|
|
223
|
+
let srcHandle;
|
|
224
|
+
let destHandle;
|
|
225
|
+
const tmpName = `${resolvedDest}.tmp-${process.pid}-${Date.now()}`;
|
|
226
|
+
try {
|
|
227
|
+
srcHandle = await fs.promises.open(resolvedSrc, srcFlags);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const stats = await srcHandle.stat();
|
|
234
|
+
if (stats.isDirectory()) {
|
|
235
|
+
await srcHandle.close();
|
|
236
|
+
throw new Error("Source is a directory");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await createDirIfNotExists(path.dirname(resolvedDest));
|
|
240
|
+
|
|
241
|
+
destHandle = await fs.promises.open(
|
|
242
|
+
tmpName,
|
|
243
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC,
|
|
244
|
+
stats.mode,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const bufferSize = 64 * 1024;
|
|
248
|
+
const buffer = Buffer.allocUnsafe(bufferSize);
|
|
249
|
+
let position = 0;
|
|
250
|
+
while (true) {
|
|
251
|
+
const { bytesRead } = await srcHandle.read(
|
|
252
|
+
buffer,
|
|
253
|
+
0,
|
|
254
|
+
bufferSize,
|
|
255
|
+
position,
|
|
256
|
+
);
|
|
257
|
+
if (!bytesRead) break;
|
|
258
|
+
let written = 0;
|
|
259
|
+
while (written < bytesRead) {
|
|
260
|
+
const { bytesWritten } = await destHandle.write(
|
|
261
|
+
buffer,
|
|
262
|
+
written,
|
|
263
|
+
bytesRead - written,
|
|
264
|
+
);
|
|
265
|
+
written += bytesWritten;
|
|
266
|
+
}
|
|
267
|
+
position += bytesRead;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await destHandle.sync();
|
|
271
|
+
await destHandle.close();
|
|
272
|
+
await srcHandle.close();
|
|
273
|
+
|
|
274
|
+
await fs.promises.rename(tmpName, resolvedDest);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
try {
|
|
277
|
+
if (destHandle) await destHandle.close();
|
|
278
|
+
} catch (e) {}
|
|
279
|
+
try {
|
|
280
|
+
await fs.promises.unlink(tmpName).catch(() => {});
|
|
281
|
+
} catch (e) {}
|
|
282
|
+
try {
|
|
283
|
+
if (srcHandle) await srcHandle.close();
|
|
284
|
+
} catch (e) {}
|
|
285
|
+
throw err;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const promptForFolderName = async () => {
|
|
290
|
+
const answer = await askQuestion(
|
|
291
|
+
"AFront: Enter the name of the destination folder: ",
|
|
292
|
+
);
|
|
293
|
+
return answer;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const promptForReplace = async (dirPath) => {
|
|
297
|
+
const answer = await askQuestion(
|
|
298
|
+
`The directory ${dirPath} already exists. Do you want to replace it? (yes/no): `,
|
|
299
|
+
);
|
|
300
|
+
return answer === "yes" || answer === "y";
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const removeDir = (dirPath) => {
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
console.log(`Removing existing directory: ${dirPath}`);
|
|
306
|
+
safeRm(dirPath, (err) => {
|
|
307
|
+
if (err) return reject(err);
|
|
308
|
+
resolve();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const safeReaddir = async (dirPath, safeRoot) => {
|
|
314
|
+
const resolvedDir = await fs.promises.realpath(dirPath);
|
|
315
|
+
const resolvedRoot = await fs.promises.realpath(safeRoot);
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
!resolvedDir.startsWith(resolvedRoot + path.sep) &&
|
|
319
|
+
resolvedDir !== resolvedRoot
|
|
320
|
+
) {
|
|
321
|
+
throw new Error(`Blocked readdir outside safe root: ${resolvedDir}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const stats = await fs.promises.lstat(resolvedDir);
|
|
325
|
+
if (!stats.isDirectory()) {
|
|
326
|
+
throw new Error(`Not a directory: ${resolvedDir}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return fs.promises.readdir(resolvedDir);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const moveFiles = async (srcPath, destPath, safeRoot) => {
|
|
333
|
+
const files = await safeReaddir(srcPath, safeRoot);
|
|
334
|
+
|
|
335
|
+
for (const file of files) {
|
|
336
|
+
const fileName = path.basename(file);
|
|
337
|
+
if (SKIP_FILES.includes(fileName)) continue;
|
|
338
|
+
|
|
339
|
+
const resolvedSrc = path.resolve(srcPath, file);
|
|
340
|
+
const resolvedDest = path.resolve(destPath, file);
|
|
341
|
+
|
|
342
|
+
const stats = await fs.promises.lstat(resolvedSrc);
|
|
343
|
+
|
|
344
|
+
if (stats.isSymbolicLink()) {
|
|
345
|
+
console.warn(`Skipping symbolic link: ${resolvedSrc}`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (stats.isDirectory()) {
|
|
350
|
+
await createDirIfNotExists(resolvedDest);
|
|
351
|
+
await moveFiles(resolvedSrc, resolvedDest, safeRoot);
|
|
352
|
+
} else {
|
|
353
|
+
await createDirIfNotExists(path.dirname(resolvedDest));
|
|
354
|
+
await safeMoveFile(resolvedSrc, resolvedDest);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const main = async () => {
|
|
360
|
+
try {
|
|
361
|
+
const zipPath = path.join(tmpDir, "archive.zip");
|
|
362
|
+
|
|
363
|
+
let folderName = process.argv[2];
|
|
364
|
+
if (folderName === ".") {
|
|
365
|
+
folderName = path.basename(process.cwd());
|
|
366
|
+
} else if (!folderName) {
|
|
367
|
+
folderName = await promptForFolderName();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Sanitize the provided folder name to prevent path traversal or absolute paths
|
|
371
|
+
const sanitizeFolderName = (name) => {
|
|
372
|
+
if (!name) return "";
|
|
373
|
+
// If user provided '.', use current dir basename
|
|
374
|
+
if (name === ".") return path.basename(process.cwd());
|
|
375
|
+
// Disallow absolute paths
|
|
376
|
+
if (path.isAbsolute(name)) {
|
|
377
|
+
console.warn(
|
|
378
|
+
"Absolute paths are not allowed for destination; using basename.",
|
|
379
|
+
);
|
|
380
|
+
name = path.basename(name);
|
|
381
|
+
}
|
|
382
|
+
// Disallow parent traversal and nested paths
|
|
383
|
+
if (name === ".." || name.includes(path.sep) || name.includes("..")) {
|
|
384
|
+
console.warn(
|
|
385
|
+
"Path traversal detected in destination name; using basename of input.",
|
|
386
|
+
);
|
|
387
|
+
name = path.basename(name);
|
|
388
|
+
}
|
|
389
|
+
// Finally, ensure it's a single path segment
|
|
390
|
+
name = path.basename(name);
|
|
391
|
+
if (!name) throw new Error("Invalid destination folder name");
|
|
392
|
+
return name;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
folderName = sanitizeFolderName(folderName);
|
|
396
|
+
|
|
397
|
+
console.log(
|
|
398
|
+
chalk.green(`Creating project in ${chalk.bold(`./${folderName}`)}\n`),
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const destDir = path.join(process.cwd(), folderName);
|
|
402
|
+
|
|
403
|
+
if (fs.existsSync(destDir)) {
|
|
404
|
+
const replace = await promptForReplace(destDir);
|
|
405
|
+
if (replace) {
|
|
406
|
+
await removeDir(destDir);
|
|
407
|
+
await createDirIfNotExists(destDir);
|
|
408
|
+
} else {
|
|
409
|
+
console.log("Operation aborted.");
|
|
410
|
+
rl.close();
|
|
411
|
+
process.exit(0);
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
await createDirIfNotExists(destDir);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
await downloadFile(GITHUB_ZIP_URL, zipPath);
|
|
418
|
+
|
|
419
|
+
await extractZip(zipPath, tmpDir);
|
|
420
|
+
|
|
421
|
+
const extractedItems = fs.readdirSync(tmpDir);
|
|
422
|
+
|
|
423
|
+
const extractedFolderName = extractedItems.find((item) => {
|
|
424
|
+
const fullPath = path.join(tmpDir, item);
|
|
425
|
+
return fs.lstatSync(fullPath).isDirectory();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (!extractedFolderName) {
|
|
429
|
+
throw new Error("Extraction failed: no directory found in archive.");
|
|
430
|
+
}
|
|
431
|
+
const extractedFolderPath = path.join(tmpDir, extractedFolderName);
|
|
432
|
+
|
|
433
|
+
await moveFiles(extractedFolderPath, destDir, tmpDir);
|
|
434
|
+
|
|
435
|
+
// Remove any bundled lockfiles from the extracted archive to avoid integrity
|
|
436
|
+
// checksum mismatches originating from the release zip's lockfile.
|
|
437
|
+
try {
|
|
438
|
+
await safeUnlink(path.join(destDir, "package-lock.json")).catch(() => {});
|
|
439
|
+
await safeUnlink(path.join(destDir, "npm-shrinkwrap.json")).catch(
|
|
440
|
+
() => {},
|
|
441
|
+
);
|
|
442
|
+
} catch (e) {
|
|
443
|
+
// ignore
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
await runNpmInstall(destDir);
|
|
447
|
+
|
|
448
|
+
console.log(chalk.green("\nā AFront project created successfully!\n"));
|
|
449
|
+
|
|
450
|
+
console.log(chalk.bold("Next steps:"));
|
|
451
|
+
console.log(chalk.cyan(` cd ${folderName}`));
|
|
452
|
+
console.log(chalk.cyan(" npm start\n"));
|
|
453
|
+
|
|
454
|
+
console.log(chalk.gray("Happy building with AFront š"));
|
|
455
|
+
|
|
456
|
+
rl.close();
|
|
457
|
+
|
|
458
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
459
|
+
process.exit(0);
|
|
460
|
+
} catch (err) {
|
|
461
|
+
console.error(chalk.red("ā Error:"), err.message);
|
|
462
|
+
rl.close();
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const showBanner = () => {
|
|
468
|
+
console.log(
|
|
469
|
+
chalk.cyan.white(`
|
|
470
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
471
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
472
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
473
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
474
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
475
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāā
|
|
476
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāā
|
|
477
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāā
|
|
478
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāā
|
|
479
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāā
|
|
480
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāā
|
|
481
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāā
|
|
482
|
+
āāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāā
|
|
483
|
+
āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāā
|
|
484
|
+
āāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāā
|
|
485
|
+
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
486
|
+
āāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
487
|
+
āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
488
|
+
āāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
489
|
+
āāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
490
|
+
āāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
491
|
+
āāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
492
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
493
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
494
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
495
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
496
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
497
|
+
|
|
498
|
+
`),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
console.log(
|
|
502
|
+
chalk.cyan.bold(`
|
|
503
|
+
āāāāāā āāāāāāāāāāāāāāā āāāāāāā āāāā āāāāāāāāāāāā
|
|
504
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāā
|
|
505
|
+
āāāāāāāāāāāāāā āāāāāāāāāāā āāāāāāāāā āāā āāā
|
|
506
|
+
āāāāāāāāāāāāāā āāāāāāāāāāā āāāāāāāāāāāāā āāā
|
|
507
|
+
āāā āāāāāā āāā āāāāāāāāāāāāāāā āāāāāā āāā
|
|
508
|
+
āāā āāāāāā āāā āāā āāāāāāā āāā āāāāā āāā
|
|
509
|
+
`),
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
console.log(chalk.cyan.bold(`ā” AFront v${VERSION}\n`));
|
|
513
|
+
console.log(chalk.gray("The Future-Ready Frontend Framework\n"));
|
|
514
|
+
console.log(chalk.gray("š https://afront.dev\n"));
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
showBanner();
|
|
518
|
+
main();
|