wad-lab 1.0.2 → 1.0.4
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/bin/index.js
CHANGED
|
@@ -4,6 +4,29 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
|
|
7
|
+
function copyDirRecursive(src, dest) {
|
|
8
|
+
if (typeof fs.cpSync === 'function') {
|
|
9
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(dest)) {
|
|
14
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const srcPath = path.join(src, entry.name);
|
|
20
|
+
const destPath = path.join(dest, entry.name);
|
|
21
|
+
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
copyDirRecursive(srcPath, destPath);
|
|
24
|
+
} else {
|
|
25
|
+
fs.copyFileSync(srcPath, destPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
const templates = [
|
|
8
31
|
{ name: 'all (Unpack All 17 Templates)', value: 'all' },
|
|
9
32
|
{ name: 'html-css-js (Form Validation & DOM)', value: 'html-css-js' },
|
|
@@ -28,52 +51,64 @@ const templates = [
|
|
|
28
51
|
];
|
|
29
52
|
|
|
30
53
|
let selectedIndex = 0;
|
|
54
|
+
const selectedValues = new Set();
|
|
31
55
|
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
56
|
+
let isPrompting = false;
|
|
32
57
|
|
|
33
58
|
function renderMenu() {
|
|
34
59
|
console.clear();
|
|
35
60
|
console.log('=== WAD LAB STARTER TEMPLATES ===');
|
|
36
|
-
console.log('Use [UP / DOWN] arrow keys to
|
|
61
|
+
console.log('Use [UP / DOWN] arrow keys to move');
|
|
62
|
+
console.log('Press [SPACE] to select / deselect (multi-select supported)');
|
|
63
|
+
console.log('Press [ENTER] to confirm and proceed\n');
|
|
37
64
|
|
|
38
65
|
templates.forEach((t, i) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
const isCursor = i === selectedIndex;
|
|
67
|
+
const isChecked = selectedValues.has(t.value);
|
|
68
|
+
const box = isChecked ? '[x]' : '[ ]';
|
|
69
|
+
const prefix = isCursor ? '> ' : ' ';
|
|
70
|
+
console.log(prefix + box + ' ' + t.name);
|
|
44
71
|
});
|
|
45
72
|
|
|
46
73
|
console.log('\nPress Ctrl+C to cancel.');
|
|
47
74
|
}
|
|
48
75
|
|
|
49
|
-
function promptFolderName(
|
|
76
|
+
function promptFolderName(choices) {
|
|
77
|
+
isPrompting = true;
|
|
78
|
+
process.stdin.removeAllListeners('keypress');
|
|
50
79
|
if (process.stdin.isTTY) {
|
|
51
80
|
process.stdin.setRawMode(false);
|
|
52
81
|
}
|
|
53
82
|
|
|
54
|
-
const
|
|
83
|
+
const isAll = choices.includes('all');
|
|
84
|
+
const defaultFolder = isAll || choices.length > 1 ? 'wad-templates' : choices[0];
|
|
85
|
+
|
|
55
86
|
const rl = readline.createInterface({
|
|
56
87
|
input: process.stdin,
|
|
57
88
|
output: process.stdout
|
|
58
89
|
});
|
|
59
90
|
|
|
60
|
-
console.log('\nSelected: ' +
|
|
91
|
+
console.log('\nSelected to unpack: ' + choices.join(', '));
|
|
61
92
|
rl.question(`Enter destination folder name (default: "${defaultFolder}"): `, (answer) => {
|
|
62
93
|
rl.close();
|
|
63
94
|
const folder = answer.trim() || defaultFolder;
|
|
64
|
-
const
|
|
95
|
+
const destDir = path.resolve(process.cwd(), folder);
|
|
65
96
|
|
|
66
|
-
if (!fs.existsSync(
|
|
67
|
-
fs.mkdirSync(
|
|
97
|
+
if (!fs.existsSync(destDir)) {
|
|
98
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
68
99
|
}
|
|
69
100
|
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
console.log('\nSuccessfully unpacked ALL templates into: ' +
|
|
101
|
+
if (isAll) {
|
|
102
|
+
copyDirRecursive(templatesRoot, destDir);
|
|
103
|
+
console.log('\nSuccessfully unpacked ALL templates into: ' + destDir);
|
|
73
104
|
} else {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
choices.forEach(c => {
|
|
106
|
+
const src = path.join(templatesRoot, c);
|
|
107
|
+
const dest = path.join(destDir, c);
|
|
108
|
+
copyDirRecursive(src, dest);
|
|
109
|
+
console.log('Unpacked [' + c + '] into: ' + dest);
|
|
110
|
+
});
|
|
111
|
+
console.log('\nSuccessfully unpacked all chosen templates into: ' + destDir);
|
|
77
112
|
}
|
|
78
113
|
process.exit(0);
|
|
79
114
|
});
|
|
@@ -83,7 +118,7 @@ if (process.argv[2]) {
|
|
|
83
118
|
const arg = process.argv[2];
|
|
84
119
|
const found = templates.find(t => t.value === arg);
|
|
85
120
|
if (found) {
|
|
86
|
-
promptFolderName(found.value);
|
|
121
|
+
promptFolderName([found.value]);
|
|
87
122
|
} else {
|
|
88
123
|
console.error('Unknown template: ' + arg);
|
|
89
124
|
process.exit(1);
|
|
@@ -97,6 +132,8 @@ if (process.argv[2]) {
|
|
|
97
132
|
renderMenu();
|
|
98
133
|
|
|
99
134
|
process.stdin.on('keypress', (str, key) => {
|
|
135
|
+
if (isPrompting) return;
|
|
136
|
+
|
|
100
137
|
if (key.ctrl && key.name === 'c') {
|
|
101
138
|
process.exit(0);
|
|
102
139
|
}
|
|
@@ -107,8 +144,29 @@ if (process.argv[2]) {
|
|
|
107
144
|
} else if (key.name === 'down') {
|
|
108
145
|
selectedIndex = (selectedIndex + 1) % templates.length;
|
|
109
146
|
renderMenu();
|
|
147
|
+
} else if (key.name === 'space') {
|
|
148
|
+
const currentVal = templates[selectedIndex].value;
|
|
149
|
+
if (currentVal === 'all') {
|
|
150
|
+
if (selectedValues.has('all')) {
|
|
151
|
+
selectedValues.clear();
|
|
152
|
+
} else {
|
|
153
|
+
selectedValues.clear();
|
|
154
|
+
selectedValues.add('all');
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
selectedValues.delete('all');
|
|
158
|
+
if (selectedValues.has(currentVal)) {
|
|
159
|
+
selectedValues.delete(currentVal);
|
|
160
|
+
} else {
|
|
161
|
+
selectedValues.add(currentVal);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
renderMenu();
|
|
110
165
|
} else if (key.name === 'return' || key.name === 'enter') {
|
|
111
|
-
|
|
166
|
+
let chosen = Array.from(selectedValues);
|
|
167
|
+
if (chosen.length === 0) {
|
|
168
|
+
chosen = [templates[selectedIndex].value];
|
|
169
|
+
}
|
|
112
170
|
promptFolderName(chosen);
|
|
113
171
|
}
|
|
114
172
|
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wad-lab",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Interactive CLI for WAD Lab starter templates
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Interactive CLI for WAD Lab starter templates compatible with Node 12+",
|
|
5
5
|
"bin": {
|
|
6
6
|
"wad-lab": "./bin/index.js"
|
|
7
7
|
},
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=12.0.0"
|
|
10
|
+
},
|
|
8
11
|
"keywords": [
|
|
9
12
|
"wad",
|
|
10
13
|
"spring-boot",
|
|
@@ -2,26 +2,7 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>Form Validation Demo</title>
|
|
5
|
-
<style>
|
|
6
|
-
body {
|
|
7
|
-
font-family: sans-serif;
|
|
8
|
-
margin: 20px;
|
|
9
|
-
}
|
|
10
|
-
input:focus {
|
|
11
|
-
outline: 2px solid black;
|
|
12
|
-
}
|
|
13
|
-
input:invalid {
|
|
14
|
-
border: 1px solid gray;
|
|
15
|
-
}
|
|
16
|
-
p::before {
|
|
17
|
-
content: "• ";
|
|
18
|
-
}
|
|
19
|
-
table, th, td {
|
|
20
|
-
border: 1px solid black;
|
|
21
|
-
border-collapse: collapse;
|
|
22
|
-
padding: 4px 8px;
|
|
23
|
-
}
|
|
24
|
-
</style>
|
|
5
|
+
<link rel="stylesheet" href="style.css">
|
|
25
6
|
</head>
|
|
26
7
|
<body>
|
|
27
8
|
<h2>Item Entry Form</h2>
|
|
@@ -56,45 +37,6 @@
|
|
|
56
37
|
<tbody id="tableBody"></tbody>
|
|
57
38
|
</table>
|
|
58
39
|
|
|
59
|
-
<script>
|
|
60
|
-
function validateAndSubmit(e) {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
|
|
63
|
-
const name = document.getElementById("name").value.trim();
|
|
64
|
-
const description = document.getElementById("description").value.trim();
|
|
65
|
-
const price = document.getElementById("price").value.trim();
|
|
66
|
-
const errorMsg = document.getElementById("errorMsg");
|
|
67
|
-
|
|
68
|
-
errorMsg.innerText = "";
|
|
69
|
-
|
|
70
|
-
if (name.length < 2) {
|
|
71
|
-
errorMsg.innerText = "Name must be at least 2 characters.";
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const priceNum = parseFloat(price);
|
|
76
|
-
if (isNaN(priceNum) || priceNum <= 0) {
|
|
77
|
-
errorMsg.innerText = "Price must be a positive number.";
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const tbody = document.getElementById("tableBody");
|
|
82
|
-
const row = document.createElement("tr");
|
|
83
|
-
|
|
84
|
-
row.innerHTML = "<td>" + name + "</td>" +
|
|
85
|
-
"<td>" + description + "</td>" +
|
|
86
|
-
"<td>" + priceNum.toFixed(2) + "</td>" +
|
|
87
|
-
"<td><button onclick='deleteRow(this)'>Delete</button></td>";
|
|
88
|
-
|
|
89
|
-
tbody.appendChild(row);
|
|
90
|
-
|
|
91
|
-
document.getElementById("itemForm").reset();
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function deleteRow(btn) {
|
|
96
|
-
btn.parentElement.parentElement.remove();
|
|
97
|
-
}
|
|
98
|
-
</script>
|
|
40
|
+
<script src="script.js"></script>
|
|
99
41
|
</body>
|
|
100
42
|
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function validateAndSubmit(e) {
|
|
2
|
+
e.preventDefault();
|
|
3
|
+
|
|
4
|
+
const name = document.getElementById("name").value.trim();
|
|
5
|
+
const description = document.getElementById("description").value.trim();
|
|
6
|
+
const price = document.getElementById("price").value.trim();
|
|
7
|
+
const errorMsg = document.getElementById("errorMsg");
|
|
8
|
+
|
|
9
|
+
errorMsg.innerText = "";
|
|
10
|
+
|
|
11
|
+
if (name.length < 2) {
|
|
12
|
+
errorMsg.innerText = "Name must be at least 2 characters.";
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const priceNum = parseFloat(price);
|
|
17
|
+
if (isNaN(priceNum) || priceNum <= 0) {
|
|
18
|
+
errorMsg.innerText = "Price must be a positive number.";
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tbody = document.getElementById("tableBody");
|
|
23
|
+
const row = document.createElement("tr");
|
|
24
|
+
|
|
25
|
+
row.innerHTML = "<td>" + name + "</td>" +
|
|
26
|
+
"<td>" + description + "</td>" +
|
|
27
|
+
"<td>" + priceNum.toFixed(2) + "</td>" +
|
|
28
|
+
"<td><button onclick='deleteRow(this)'>Delete</button></td>";
|
|
29
|
+
|
|
30
|
+
tbody.appendChild(row);
|
|
31
|
+
|
|
32
|
+
document.getElementById("itemForm").reset();
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function deleteRow(btn) {
|
|
37
|
+
btn.parentElement.parentElement.remove();
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: sans-serif;
|
|
3
|
+
margin: 20px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
input:focus {
|
|
7
|
+
outline: 2px solid black;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
input:invalid {
|
|
11
|
+
border: 1px solid gray;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
p::before {
|
|
15
|
+
content: "• ";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
table, th, td {
|
|
19
|
+
border: 1px solid black;
|
|
20
|
+
border-collapse: collapse;
|
|
21
|
+
padding: 4px 8px;
|
|
22
|
+
}
|