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 navigate, press [ENTER] to select:\n');
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
- if (i === selectedIndex) {
40
- console.log('> [*] ' + t.name);
41
- } else {
42
- console.log(' [ ] ' + t.name);
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(choice) {
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 defaultFolder = choice === 'all' ? 'wad-all-templates' : choice;
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: ' + choice);
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 dest = path.resolve(process.cwd(), folder);
95
+ const destDir = path.resolve(process.cwd(), folder);
65
96
 
66
- if (!fs.existsSync(dest)) {
67
- fs.mkdirSync(dest, { recursive: true });
97
+ if (!fs.existsSync(destDir)) {
98
+ fs.mkdirSync(destDir, { recursive: true });
68
99
  }
69
100
 
70
- if (choice === 'all') {
71
- fs.cpSync(templatesRoot, dest, { recursive: true });
72
- console.log('\nSuccessfully unpacked ALL templates into: ' + dest);
101
+ if (isAll) {
102
+ copyDirRecursive(templatesRoot, destDir);
103
+ console.log('\nSuccessfully unpacked ALL templates into: ' + destDir);
73
104
  } else {
74
- const src = path.join(templatesRoot, choice);
75
- fs.cpSync(src, dest, { recursive: true });
76
- console.log('\nSuccessfully unpacked [' + choice + '] into: ' + dest);
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
- const chosen = templates[selectedIndex].value;
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.2",
4
- "description": "Interactive CLI for WAD Lab starter templates (Spring Boot, MongoDB, Vue 3, Servlets)",
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
+ }