sunmeat 1.0.1 → 1.0.3
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/index.js +279 -15
- package/package.json +1 -1
- package/test.js +12 -0
package/index.js
CHANGED
|
@@ -1,25 +1,289 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const readline = require("readline");
|
|
3
4
|
const boxen = require("boxen");
|
|
4
5
|
const chalk = require("chalk");
|
|
5
6
|
|
|
6
|
-
const c = chalk
|
|
7
|
+
const c = chalk;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// ─── COLOUR PALETTE ───────────────────────────────────────────────────────────
|
|
10
|
+
const accent = c.hex("#5BB0F7").bold;
|
|
11
|
+
const dim = c.gray;
|
|
12
|
+
const hi = c.white.bold;
|
|
13
|
+
const muted = c.hex("#8ab4f8");
|
|
14
|
+
const ok = c.green.bold;
|
|
15
|
+
const warn = c.yellow;
|
|
16
|
+
const err = c.red.bold;
|
|
17
|
+
const ukraine1 = c.hex("#005BBB").bold;
|
|
18
|
+
const ukraine2 = c.hex("#FFD500").bold;
|
|
19
|
+
|
|
20
|
+
// ─── HELPERS ──────────────────────────────────────────────────────────────────
|
|
21
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22
|
+
|
|
23
|
+
async function typewrite(text, delay = 18) {
|
|
24
|
+
process.stdout.write("\n");
|
|
25
|
+
for (const ch of text) {
|
|
26
|
+
process.stdout.write(ch);
|
|
27
|
+
await sleep(delay);
|
|
28
|
+
}
|
|
29
|
+
process.stdout.write("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function bar(filled, total = 20, filledChar = "█", emptyChar = "░") {
|
|
33
|
+
return (
|
|
34
|
+
accent(filledChar.repeat(filled)) +
|
|
35
|
+
dim(emptyChar.repeat(total - filled))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function section(title) {
|
|
40
|
+
console.log("\n" + accent("┌─ ") + hi(title) + accent(" " + "─".repeat(42 - title.length)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── DATA ─────────────────────────────────────────────────────────────────────
|
|
44
|
+
const profile = {
|
|
45
|
+
name: "Oleksandr Zahoruiko",
|
|
46
|
+
alias: "sunmeat",
|
|
47
|
+
age: 36,
|
|
48
|
+
location: "Odesa, Ukraine 🇺🇦",
|
|
49
|
+
role: "Software Engineer & Lecturer",
|
|
50
|
+
org: "IT Academy Step — Leading Teacher Specialist",
|
|
51
|
+
since: "2007",
|
|
52
|
+
website: "http://sunmeat.site/",
|
|
53
|
+
linkedin: "https://www.linkedin.com/in/sunmeat/",
|
|
54
|
+
github: "https://github.com/sunmeat",
|
|
55
|
+
npx: "npx sunmeat@latest",
|
|
13
56
|
};
|
|
14
57
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
58
|
+
const languages = [
|
|
59
|
+
{ name: "C++", level: 18, label: "Systems & Performance" },
|
|
60
|
+
{ name: "C#", level: 18, label: ".NET / ASP.NET Core" },
|
|
61
|
+
{ name: "Java", level: 16, label: "Android & Enterprise" },
|
|
62
|
+
{ name: "JavaScript", level: 17, label: "Fullstack Web" },
|
|
63
|
+
{ name: "Python", level: 15, label: "Backend & Automation" },
|
|
64
|
+
{ name: "SQL", level: 17, label: "Databases" },
|
|
65
|
+
{ name: "PHP", level: 13, label: "Server-side Web" },
|
|
66
|
+
{ name: "Kotlin", level: 12, label: "Android" },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const frameworks = {
|
|
70
|
+
backend: ["ASP.NET Core", "EF Core", "Dapper", "Spring", "Django", "Node.js"],
|
|
71
|
+
frontend: ["React", "Angular", "Electron.js"],
|
|
72
|
+
mobile: ["Java Android", "Kotlin Android", "MAUI"],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const hobbies = [
|
|
76
|
+
{ icon: "🎹", name: "Music", detail: "accordion · piano · melodica · sopilka · flute · drymba · kalimba · ukulele · guitar" },
|
|
77
|
+
{ icon: "🎤", name: "Karaoke", detail: "syntax errors don't exist in song" },
|
|
78
|
+
{ icon: "📷", name: "Photography", detail: "Carpathians, Bulgaria, Amsterdam..." },
|
|
79
|
+
{ icon: "🌿", name: "Gardening", detail: "same patience as debugging" },
|
|
80
|
+
{ icon: "🪙", name: "Coin Collecting",detail: "history in the palm of your hand" },
|
|
81
|
+
{ icon: "🎲", name: "Board Games", detail: "Munchkin — playing like a Boss" },
|
|
82
|
+
{ icon: "🎨", name: "Acrylic Paint", detail: "creative chaos, proudly committed" },
|
|
83
|
+
{ icon: "✈️", name: "Travel", detail: "waiting for Ukraine's victory to explore the world again" },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const quotes = [
|
|
87
|
+
{ text: "The unreal is more powerful than the real…", author: "Chuck Palahniuk" },
|
|
88
|
+
{ text: "Stay hungry. Stay foolish.", author: "Steve Jobs" },
|
|
89
|
+
{ text: "Great minds discuss ideas; average minds discuss events; small minds discuss people.", author: "Eleanor Roosevelt" },
|
|
90
|
+
{ text: "The best error message is the one that never shows up.", author: "Thomas Fuchs" },
|
|
91
|
+
{ text: "Code is like humour. When you have to explain it, it's bad.", author: "Cory House" },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// ─── SCREENS ──────────────────────────────────────────────────────────────────
|
|
95
|
+
function showWelcome() {
|
|
96
|
+
console.clear();
|
|
97
|
+
const banner = [
|
|
98
|
+
ukraine1(" █████╗ ██╗ ███████╗██╗ ██╗ "),
|
|
99
|
+
ukraine2(" ██╔══██╗██║ ██╔════╝╚██╗██╔╝ "),
|
|
100
|
+
ukraine1(" ███████║██║ █████╗ ╚███╔╝ "),
|
|
101
|
+
ukraine2(" ██╔══██║██║ ██╔══╝ ██╔██╗ "),
|
|
102
|
+
ukraine1(" ██║ ██║███████╗███████╗██╔╝ ██╗ "),
|
|
103
|
+
ukraine2(" ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ "),
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
const card = `
|
|
107
|
+
${banner}
|
|
108
|
+
|
|
109
|
+
${hi("Oleksandr Zahoruiko")} ${dim("aka")} ${accent("sunmeat")}
|
|
110
|
+
${muted("Software Engineer · Lecturer · Developer")}
|
|
111
|
+
${dim("📍 Odesa, Ukraine • IT Academy Step since 2007")}
|
|
112
|
+
|
|
113
|
+
${dim("website →")} ${muted(profile.website)}
|
|
114
|
+
${dim("github →")} ${muted(profile.github)}
|
|
115
|
+
${dim("linkedin →")} ${muted(profile.linkedin)}
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
console.log(
|
|
119
|
+
boxen(card, {
|
|
120
|
+
padding: 1,
|
|
121
|
+
margin: { top: 1, left: 2 },
|
|
122
|
+
borderStyle: "double",
|
|
123
|
+
borderColor: "#5BB0F7",
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function showAbout() {
|
|
129
|
+
console.clear();
|
|
130
|
+
section("About Me");
|
|
131
|
+
console.log(
|
|
132
|
+
boxen(
|
|
133
|
+
`
|
|
134
|
+
${hi("Oleksandr Zahoruiko")} ${dim("·")} ${accent("age 36")} ${dim("·")} ${muted("Odesa, Ukraine 🇺🇦")}
|
|
135
|
+
|
|
136
|
+
${dim("I'm an educator, lecturer and software engineer.")}
|
|
137
|
+
${dim("Graduated with honours in 2010 (Economics & Programming).")}
|
|
138
|
+
${dim("Since 2007 — Leading Teacher Specialist at IT Academy Step.")}
|
|
139
|
+
|
|
140
|
+
${c.italic.gray('"engaging conversationalist, responsible, hardworking,')}
|
|
141
|
+
${c.italic.gray('optimistic, cheerful, creative, friendly, and modestly humble"')}
|
|
142
|
+
${dim(" — friends & colleagues")}
|
|
143
|
+
|
|
144
|
+
${ukraine1("🇺🇦")} ${warn("Waiting for Ukraine's victory and borders to reopen.")}
|
|
145
|
+
${dim(" Travel is one of my greatest passions.")}
|
|
146
|
+
`,
|
|
147
|
+
{ padding: 1, borderStyle: "round", borderColor: "#5BB0F7" }
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function showSkills() {
|
|
153
|
+
console.clear();
|
|
154
|
+
section("Skills — Languages");
|
|
155
|
+
console.log();
|
|
156
|
+
for (const lang of languages) {
|
|
157
|
+
const namepad = lang.name.padEnd(13);
|
|
158
|
+
console.log(
|
|
159
|
+
` ${accent(namepad)} ${bar(lang.level)} ${dim(" " + lang.label)}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
section("Frameworks & Libraries");
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(` ${hi("Backend :")} ${muted(frameworks.backend.join(" · "))}`);
|
|
166
|
+
console.log(` ${hi("Frontend:")} ${muted(frameworks.frontend.join(" · "))}`);
|
|
167
|
+
console.log(` ${hi("Mobile :")} ${muted(frameworks.mobile.join(" · "))}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function showHobbies() {
|
|
171
|
+
console.clear();
|
|
172
|
+
section("Hobbies & Passions");
|
|
173
|
+
console.log();
|
|
174
|
+
for (const h of hobbies) {
|
|
175
|
+
console.log(
|
|
176
|
+
` ${h.icon} ${hi(h.name.padEnd(18))} ${dim(h.detail)}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function showQuote() {
|
|
182
|
+
console.clear();
|
|
183
|
+
const q = quotes[Math.floor(Math.random() * quotes.length)];
|
|
184
|
+
section("Random Quote");
|
|
185
|
+
console.log(
|
|
186
|
+
boxen(
|
|
187
|
+
`\n ${c.italic.white('"' + q.text + '"')}\n\n ${dim("— " + q.author)}\n`,
|
|
188
|
+
{ padding: 1, borderStyle: "round", borderColor: "#FFD500" }
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
console.log(dim(" (press Enter for another one from the menu)"));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function showContact() {
|
|
195
|
+
console.clear();
|
|
196
|
+
section("Contact");
|
|
197
|
+
await typewrite(
|
|
198
|
+
` Say hi 👋 → ${profile.website}`,
|
|
199
|
+
20
|
|
200
|
+
);
|
|
201
|
+
console.log(
|
|
202
|
+
boxen(
|
|
203
|
+
`
|
|
204
|
+
${hi("🌐 Website ")} ${muted(profile.website)}
|
|
205
|
+
${hi("🐱 GitHub ")} ${muted(profile.github)}
|
|
206
|
+
${hi("💼 LinkedIn ")} ${muted(profile.linkedin)}
|
|
207
|
+
|
|
208
|
+
${dim("npx card ")} ${accent(profile.npx)}
|
|
209
|
+
`,
|
|
210
|
+
{ padding: 1, borderStyle: "round", borderColor: "green" }
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─── MENU ─────────────────────────────────────────────────────────────────────
|
|
216
|
+
const MENU = [
|
|
217
|
+
{ key: "1", label: "About Me", fn: showAbout },
|
|
218
|
+
{ key: "2", label: "Skills & Tech Stack", fn: showSkills },
|
|
219
|
+
{ key: "3", label: "Hobbies & Passions", fn: showHobbies },
|
|
220
|
+
{ key: "4", label: "Random Quote", fn: showQuote },
|
|
221
|
+
{ key: "5", label: "Contact & Links", fn: showContact },
|
|
222
|
+
{ key: "0", label: "Exit", fn: null },
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
function renderMenu() {
|
|
226
|
+
console.log("\n" + accent("─".repeat(46)));
|
|
227
|
+
console.log(hi(" Choose a section:"));
|
|
228
|
+
console.log(accent("─".repeat(46)));
|
|
229
|
+
for (const item of MENU) {
|
|
230
|
+
if (item.key === "0") {
|
|
231
|
+
console.log(dim(`\n [${item.key}] ${item.label}`));
|
|
232
|
+
} else {
|
|
233
|
+
console.log(` ${accent("[" + item.key + "]")} ${muted(item.label)}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
console.log(accent("─".repeat(46)));
|
|
237
|
+
process.stdout.write(hi("\n ❯ "));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── MAIN LOOP ────────────────────────────────────────────────────────────────
|
|
241
|
+
async function main() {
|
|
242
|
+
showWelcome();
|
|
243
|
+
|
|
244
|
+
const rl = readline.createInterface({
|
|
245
|
+
input: process.stdin,
|
|
246
|
+
output: process.stdout,
|
|
247
|
+
terminal: false,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Make keypress work properly
|
|
251
|
+
if (process.stdin.isTTY) {
|
|
252
|
+
process.stdin.setRawMode(false);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const askMenu = () => {
|
|
256
|
+
renderMenu();
|
|
257
|
+
rl.once("line", async (input) => {
|
|
258
|
+
const choice = input.trim();
|
|
259
|
+
const item = MENU.find((m) => m.key === choice);
|
|
260
|
+
|
|
261
|
+
if (!item) {
|
|
262
|
+
console.log(err("\n Invalid choice. Try again."));
|
|
263
|
+
return askMenu();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (item.key === "0") {
|
|
267
|
+
console.log(
|
|
268
|
+
"\n" +
|
|
269
|
+
boxen(
|
|
270
|
+
` ${ukraine1("🇺🇦")} ${hi("Glory to Ukraine!")} ${ukraine2("Glory to the Heroes!")} \n\n ${dim("Thanks for visiting — ")}${accent("sunmeat")}`,
|
|
271
|
+
{ padding: 1, borderStyle: "round", borderColor: "#FFD500", margin: 1 }
|
|
272
|
+
)
|
|
273
|
+
);
|
|
274
|
+
rl.close();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await item.fn();
|
|
279
|
+
askMenu();
|
|
280
|
+
});
|
|
281
|
+
};
|
|
21
282
|
|
|
22
|
-
|
|
23
|
-
|
|
283
|
+
askMenu();
|
|
284
|
+
}
|
|
24
285
|
|
|
25
|
-
|
|
286
|
+
main().catch((e) => {
|
|
287
|
+
console.error(e);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
});
|
package/package.json
CHANGED